import time
import logging
import numpy as np
from datetime import datetime, timedelta
from xtquant import xtdata
import threading
from typing import Dict, List, Tuple, Optional
import warnings
import sys
import os
import pandas as pd

# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# 导入仓位管理模块
from position_management import (
    PositionManager,
    PositionCalculator,
    TradeProtector
)

# 导入配置加载器
from config_loader import ConfigLoader

# 忽略matplotlib的字体警告
warnings.filterwarnings("ignore", category=UserWarning, module="matplotlib")
warnings.filterwarnings("ignore", category=UserWarning, module="tkinter")

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# 增强matplotlib字体支持
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.size'] = 12
logger = logging.getLogger('BreakoutStrategy')

class BreakoutStrategy:
    """突破5日线策略
    
    使用类方法工厂模式创建实例：
    - BreakoutStrategy.for_live()      # 创建实盘/模拟盘实例
    - BreakoutStrategy.for_backtest()  # 创建回测实例
    """
    
    @classmethod
    def for_live(cls):
        """创建实盘/模拟盘实例（启用完整初始化）"""
        return cls(backtest_mode=False)
    
    @classmethod
    def for_backtest(cls):
        """创建回测实例（精简初始化）"""
        return cls(backtest_mode=True)
    
    def __init__(self, backtest_mode=False):
        # 加载配置文件（统一使用 config.yaml）
        config_loader = ConfigLoader()
        self.config = config_loader._config
        self.incremental_download = self.config.get('backtest', {}).get('incremental_download', True)
        
        # 初始化技术形态引擎（防止属性访问错误）
        # 注意：回测模式下，技术引擎由回测系统管理，策略不需要初始化
        self.technical_engine = None
        
        # 保存回测模式标志
        self.backtest_mode = backtest_mode
        
        # 初始化双重量能验证器（v3.4新增）
        try:
            from volume_validators import DoubleVolumeValidator
            self.volume_validator = DoubleVolumeValidator(self.config)
            logger.info("✅ 双重量能验证器初始化完成")
        except Exception as e:
            logger.warning(f"双重量能验证器初始化失败（将使用原有逻辑）: {e}")
            self.volume_validator = None
        
        # 初始化动态量能阈值计算器（v3.5新增）
        try:
            from volume_validators import DynamicVolumeThresholdCalculator
            self.dynamic_threshold_calculator = DynamicVolumeThresholdCalculator(self.config)
            logger.info("✅ 动态量能阈值计算器初始化完成")
        except Exception as e:
            logger.warning(f"动态量能阈值计算器初始化失败（将使用固定阈值）: {e}")
            self.dynamic_threshold_calculator = None
        
        # 初始化量比计算器
        try:
            from volume_validators.volume_ratio_calculator import VolumeRatioCalculator
            self.volume_ratio_calculator = VolumeRatioCalculator(self.config)
            logger.info("✅ 量比计算器初始化完成")
        except Exception as e:
            logger.warning(f"量比计算器初始化失败（将使用旧方法）: {e}")
            self.volume_ratio_calculator = None
        
        # 初始化买入敏感度分析器（复用卖出信号验证系统的分析器）
        try:
            from signal_validation.analyzers import TimeSensitivityAnalyzer, StockLiquidityAnalyzer
            from validation_config_loader import get_validation_config_loader
            
            self.sensitivity_analyzer = TimeSensitivityAnalyzer()
            self.liquidity_analyzer = StockLiquidityAnalyzer()
            validation_config = get_validation_config_loader().config
            self.buy_sensitivity_config = validation_config.get('buy_sensitivity_rules', {})
            logger.info("✅ 买入敏感度分析器初始化完成")
        except Exception as e:
            logger.warning(f"买入敏感度分析器初始化失败（将使用默认逻辑）: {e}")
            self.sensitivity_analyzer = None
            self.liquidity_analyzer = None
            self.buy_sensitivity_config = {}
        
        # 卖出去重：记录今天已检查过卖出条件的股票（同一天只处理一次卖出信号）
        self.sell_stocks = set()  # 存储股票代码（每天清空）
        # ⚠️ 注意：sell_stocks应该只在回测系统真正执行卖出后才标记，不应在log_sell_signal中标记
        
        # 日志去重：记录今天已输出过日志的股票（避免重复日志）
        self.logged_sell_signals = set()  # 存储股票代码（每天清空），仅用于日志去重
        self.logged_threshold_calculations = set()  # 存储(股票代码, cache_key)元组（每天清空），用于动态阈值计算日志去重
        
        # 日志文件（用于保存策略筛选日志）
        self.signal_log_file = None
        if backtest_mode:
            try:
                log_dir = "backtest_logs"
                if not os.path.exists(log_dir):
                    os.makedirs(log_dir)
                date_str = datetime.now().strftime('%Y%m%d')
                log_filename = os.path.join(log_dir, f"backtest_{date_str}.txt")
                self.signal_log_file = open(log_filename, 'a', encoding='utf-8')
                # 写入分隔符
                timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                self.signal_log_file.write(f"\n{'='*80}\n")
                self.signal_log_file.write(f"回测开始: {timestamp}\n")
                self.signal_log_file.write(f"{'='*80}\n\n")
                self.signal_log_file.flush()
            except Exception as e:
                logger.warning(f"创建日志文件失败: {e}")
        
        # 可同时指定个股与版块
        # 在模拟盘/实盘模式下，由用户在启动时动态输入
        # 在回测模式下，由回测系统通过 initialize_data 传入
        self.explicit_stocks = []
        
        # 监控板块（可选）
        # 在模拟盘/实盘模式下，由用户在启动时动态输入
        self.sectors = []

        # 启动时是否对全池进行一次筛选
        self.enable_startup_screen = False
    


        # 在回测模式下不初始化股票池，避免下载全市场数据
        if not backtest_mode:
            self.stock_pool = self.get_stock_pool()
            # 确保股票代码格式统一（大写）
            self.stock_pool = [stock.upper() for stock in self.stock_pool]
        else:
            # 回测模式下使用空股票池，后续会通过参数设置
            self.stock_pool = []
            
        self.float_shares = {}  # 存储流通股本
        self.float_shares_source = {}  # 流通股本来源：instrument/details/unavailable
        self.buy_stocks = set()  # 当日已买入的股票（买入去重）
        self.stock_strategy_types = {}  # 记录每个股票的买入策略类型（用于卖出时显示）
        self.hist_data = {}  # 存储历史K线数据（原始）
        self.hist_np_cache = {}  # 存储历史K线数据（numpy缓存: close/volume/time）
        self.today_data = {}  # 存储当日K线数据
        self.callback_lock = threading.Lock()  # 回调锁

        # ============ 仓位管理模块（使用独立模块）============
        # 导入配置
        from config_loader import (
            POSITION_CALCULATOR_CONFIG,
            TRADE_PROTECTOR_CONFIG,
            STRATEGY_CONFIG,
            BUY_PARAMS,
            SELL_PARAMS,
            MARKET_CAP_THRESHOLD,
            TREND_FILTER_CONFIG,
            TECHNICAL_PATTERNS_CONFIG
        )
        
        # 创建仓位管理器
        mode = 'backtest' if backtest_mode else 'live'
        self.position_manager = PositionManager(mode=mode)
        
        # 创建仓位计算器（使用统一配置）
        self.position_calculator = PositionCalculator(POSITION_CALCULATOR_CONFIG)
        
        # 创建交易保护器（使用统一配置）
        self.trade_protector = TradeProtector(TRADE_PROTECTOR_CONFIG)

        # ============ 双轨策略配置（从配置文件加载）============
        self.strategy_config = STRATEGY_CONFIG
        self.last_strategy_type = None  # 记录最后触发的策略类型

        # ============ 买入/卖出条件配置（从配置文件加载）============
        self.buy_params = BUY_PARAMS
        self.sell_params = SELL_PARAMS
        self.market_cap_threshold = MARKET_CAP_THRESHOLD
        
        # ============ 趋势过滤配置（硬指标，从配置文件加载）============
        self.trend_filter_config = TREND_FILTER_CONFIG
        if self.trend_filter_config.get('enabled', True):
            logger.info("✅ 趋势过滤已启用（买入/卖出硬指标）")
        else:
            logger.warning("⚠️ 趋势过滤已禁用（不推荐）")
        
        # ============ 技术形态增强配置（从配置文件加载，不允许硬编码）============
        # 从技术形态配置中读取融合增强参数和独立触发配置
        signal_enhancement = TECHNICAL_PATTERNS_CONFIG.get('signal_enhancement')
        independent_mode = TECHNICAL_PATTERNS_CONFIG.get('upper_shadow', {}).get('independent_mode')
        
        if signal_enhancement is None:
            logger.warning("⚠️ 技术形态增强配置缺失（signal_enhancement），将禁用技术形态增强功能")
            self.tech_enhancement_config = None
        else:
            enhancement_rules = signal_enhancement.get('enhancement_rules')
            if enhancement_rules is None:
                logger.error("❌ 技术形态增强规则配置缺失（enhancement_rules），禁用技术形态增强功能")
                self.tech_enhancement_config = None
            else:
                # 验证必需参数是否存在
                required_fields = ['min_strength', 'strong_strength', 'enhancement_range', 'enhancement_factor']
                missing_fields = [field for field in required_fields if field not in enhancement_rules]
                if missing_fields:
                    logger.error(f"❌ 技术形态增强配置不完整，缺少字段: {missing_fields}，禁用技术形态增强功能")
                    self.tech_enhancement_config = None
                else:
                    self.tech_enhancement_config = {
                        'min_strength': enhancement_rules['min_strength'],
                        'strong_strength': enhancement_rules['strong_strength'],
                        'enhancement_range': enhancement_rules['enhancement_range'],
                        'enhancement_factor': enhancement_rules['enhancement_factor'],
                        # 可选参数（从配置读取，如果缺失则在代码中提供默认值）
                        'strength_to_enhancement_scale': enhancement_rules.get('strength_to_enhancement_scale', 10.0),
                        'validation_impact': signal_enhancement.get('validation_impact', {}),
                        'sell_ratio_adjustment': signal_enhancement.get('sell_ratio_adjustment', {}),
                        'independent_mode': independent_mode if independent_mode else {}  # 新增：条件D配置
                    }
                    
                    # 输出配置信息
                    logger.info(f"✅ 技术形态增强配置已加载: 最小强度={self.tech_enhancement_config['min_strength']}, "
                              f"强烈强度={self.tech_enhancement_config['strong_strength']}, "
                              f"缩放因子={self.tech_enhancement_config['strength_to_enhancement_scale']}")
                    
                    # 输出条件D配置（如果启用）
                    if independent_mode and independent_mode.get('enabled', False):
                        logger.info(f"✅ 条件D(技术形态独立触发)已启用: 最低强度={independent_mode.get('min_strength', 2.5)}")
        
        # ============ 技术形态引擎（实盘模式）============
        # 注意：回测模式下，技术引擎由回测系统管理
        if not backtest_mode:
            try:
                if TECHNICAL_PATTERNS_CONFIG.get('upper_shadow', {}).get('enabled', False):
                    from technical_engine import TechnicalEngine
                    self.technical_engine = TechnicalEngine(TECHNICAL_PATTERNS_CONFIG, self)
                    logger.info("✅ 技术形态引擎已启用（实盘模式）")
            except Exception as e:
                logger.warning(f"技术形态引擎初始化失败，已禁用: {e}")
        
        # ============ 决策日志记录器 ============
        from utils import get_decision_logger, CheckResult
        self.decision_logger = get_decision_logger()
        self.CheckResult = CheckResult
        
        # 只在非回测模式下执行实时模式的初始化
        if not backtest_mode:
            # 初始化流通股本和K线数据（实时模式）
            self.initialize_data(mode="live")
            
            # 订阅实时行情
            self.subscribe_realtime_quotes()
            
            # 启动后可选的一次性全池筛选
            if self.enable_startup_screen and len(self.stock_pool) > 0:
                try:
                    self.initial_screen()
                except Exception as e:
                    logger.warning(f"启动全池筛选失败: {str(e)}")
            
            logger.info(f"策略已启动，监控 {len(self.stock_pool)} 只股票")
            logger.info("=" * 60)
            
            # 启动实时监控循环
            self.run()
        else:
            # 回测模式：不初始化数据，不订阅行情，不启动监控
            self.stock_pool = []  # 回测模式下由回测系统设置股票池
            logger.info("策略实例已创建（回测模式）")
            logger.info("=" * 60)

    # ------------------------ 工具方法：DataFrame/字典安全提取 ------------------------
    @staticmethod
    def _to_list_safe(container, key):
        """安全地将容器中的键值转换为列表"""
        try:
            if isinstance(container, dict):
                v = container.get(key, [])
                return v.tolist() if hasattr(v, 'tolist') else list(v)
            # DataFrame 或支持列访问的对象
            if key in container:
                s = container[key]
                return s.tolist() if hasattr(s, 'tolist') else list(s)
        except Exception:
            pass
        return []

    @classmethod
    def _first_non_empty(cls, container, *keys):
        """从多个可能的键中找到第一个非空的列表"""
        for k in keys:
            lst = cls._to_list_safe(container, k)
            if lst:
                return lst
        return []

    @classmethod
    def _last_time_str(cls, hist_data):
        """获取历史数据中的最后一个时间戳"""
        try:
            # 优先 time 列，否则使用索引
            if isinstance(hist_data, dict):
                times_col = hist_data.get('time', [])
            else:
                for time_key in ['time', 'Time']:
                    if time_key in hist_data:
                        times_col = hist_data[time_key]
                        break
                else:
                    times_col = getattr(hist_data, 'index', [])

            tlist = times_col.tolist() if hasattr(times_col, 'tolist') else list(times_col)
            if tlist:
                return cls._normalize_to_yyyymmdd_str(tlist[-1])
        except Exception:
            pass
        return None

    @staticmethod
    def _normalize_to_yyyymmdd_str(t):
        """将多种可能的时间表示规范为YYYY-MM-DD字符串"""
        try:
            # 数字：可能是YYYYMMDD，或秒/毫秒时间戳
            if isinstance(t, (int, float)):
                s = str(int(t))
                # 处理不同长度的时间戳
                if len(s) == 13:  # 13位：毫秒时间戳
                    return time.strftime('%Y%m%d', time.localtime(int(s) / 1000))
                elif len(s) == 10:  # 10位：秒时间戳
                    return time.strftime('%Y%m%d', time.localtime(int(s)))
                elif len(s) == 8:  # 8位：YYYYMMDD
                    return s
                else:  # 其他：取前8位
                    return s[:8] if len(s) >= 8 else s
            # 字符串：提取数字并处理
            elif isinstance(t, str):
                digits = ''.join(ch for ch in t if ch.isdigit())
                return digits[:8] if len(digits) >= 8 else digits
            # 其他对象：尝试转换为字符串后处理
            else:
                s = str(t)
                digits = ''.join(ch for ch in s if ch.isdigit())
                return digits[:8] if len(digits) >= 8 else digits
        except Exception:
            return None

    def get_stock_pool(self):
        """合并个股与板块成分为最终股票池"""
        stocks = set(s.upper() for s in self.explicit_stocks)

        # 展开板块成分
        for sector in self.sectors:
            try:
                comp = self.get_sector_components(sector)
                if comp:
                    stocks.update(c.upper() for c in comp)
                    logger.info(f"板块 {sector} 展开成分 {len(comp)} 只")
                else:
                    logger.warning(f"板块 {sector} 未取得成分或为空")
            except Exception as e:
                logger.warning(f"板块 {sector} 成分获取异常: {str(e)}")

        result = sorted(stocks)
        logger.info(f"最终股票池共 {len(result)} 只")
        return result

    def get_sector_components(self, sector_name):
        """根据板块名获取成分股，兼容常见别名/写法"""
        aliases_map = {
            '上证': ['上证A股', '沪市A股', '沪深A股', '沪市主板'],
            '科创': ['科创板', 'STAR', '科创'],
        }
        candidates = [sector_name]
        candidates.extend(aliases_map.get(sector_name, []))

        # 优先尝试研究端常用的 get_stock_list_in_sector 接口（若由xtdata提供）
        for name in candidates:
            try:
                if hasattr(xtdata, 'get_stock_list_in_sector'):
                    lst = xtdata.get_stock_list_in_sector(name)
                    if lst:
                        return lst
            except Exception:
                pass

        # 若无直接板块接口，可在此扩展：比如通过指数成分映射到板块
        # 这里留空以保持通用性
        return []

    def get_sector_stocks(self, sector):
        """获取板块股票列表"""
        try:
            if sector in ['上证', 'SH']:
                # 获取上证指数成分股
                stocks = xtdata.get_stock_list_in_sector('沪深A股')
                # 过滤出上海市场的股票
                sh_stocks = [stock for stock in stocks if stock.endswith('.SH')]
                return sh_stocks[:100]  # 限制数量，避免过多
                
            elif sector in ['深证', 'SZ']:
                # 获取深证指数成分股
                stocks = xtdata.get_stock_list_in_sector('沪深A股')
                # 过滤出深圳市场的股票
                sz_stocks = [stock for stock in stocks if stock.endswith('.SZ')]
                return sz_stocks[:100]  # 限制数量，避免过多
                
            elif sector in ['创业板', 'CYB']:
                # 获取创业板股票
                stocks = xtdata.get_stock_list_in_sector('创业板')
                return stocks[:50]  # 限制数量
                
            elif sector in ['科创板', 'KCB']:
                # 获取科创板股票
                stocks = xtdata.get_stock_list_in_sector('科创板')
                return stocks[:50]  # 限制数量
                
            else:
                # 尝试作为板块名称获取
                stocks = xtdata.get_stock_list_in_sector(sector)
                return stocks[:50]  # 限制数量
                
        except Exception as e:
            logger.warning(f"获取板块 {sector} 股票时出错: {str(e)}")
            return []

    def initialize_data(self, mode="live", target_stocks=None, target_sectors=None):
        """初始化流通股本和K线数据"""
        logger.info("正在初始化股票数据...")

        # 确定需要处理的股票列表
        stocks_to_process = []
        
        if target_stocks:
            # 验证指定股票是否有效
            valid_stocks = self._validate_stocks(target_stocks)
            if valid_stocks:
                stocks_to_process.extend(valid_stocks)
                logger.info(f"有效指定股票: {len(valid_stocks)} 只")
            else:
                logger.warning("指定的股票无效或无数据，跳过处理")
        
        if target_sectors:
            # 处理板块股票
            valid_sector_stocks = []
            for sector in target_sectors:
                sector_stocks = self.get_sector_stocks(sector)
                if sector_stocks:
                    # 验证板块股票
                    valid_sector_stocks.extend(self._validate_stocks(sector_stocks))
                    logger.info(f"板块 {sector} 有效股票: {len(valid_sector_stocks)} 只")
                else:
                    logger.warning(f"板块 {sector} 无有效股票，跳过处理")
            
            if valid_sector_stocks:
                stocks_to_process.extend(valid_sector_stocks)
        
        # 去重
        stocks_to_process = list(set(stocks_to_process))
        
        # ===== 保存用户指定的股票列表（用于后续动态更新监控范围）=====
        self.explicit_stocks = stocks_to_process.copy()
        logger.info(f"保存用户指定股票: {len(self.explicit_stocks)} 只")
        
        # 如果没有有效股票，直接返回
        if not stocks_to_process:
            if mode == "backtest":
                logger.info("回测模式：无有效目标股票，跳过数据下载")
                return
            else:
                # 实时模式下使用默认股票池
                logger.info("使用默认股票池")
                stocks_to_process = self.stock_pool
        
        logger.info(f"最终处理股票: {len(stocks_to_process)} 只")
        
        # 检查本地数据并按需下载
        self.check_and_download_data(stocks_to_process)
    
    def update_monitor_list(self, current_positions):
        """
        动态更新监控范围
        监控范围 = 用户指定股票 + 当前持仓
        
        :param current_positions: 当前持仓字典 {stock_code: position_info}
        """
        try:
            # 获取持仓股票列表
            holding_stocks = list(current_positions.keys()) if current_positions else []
            
            # 合并用户指定的股票和持仓股票
            new_monitor_list = list(set(self.explicit_stocks) | set(holding_stocks))
            
            # 计算变化
            added = set(new_monitor_list) - set(self.stock_pool)
            removed = set(self.stock_pool) - set(new_monitor_list)
            
            # 更新股票池
            old_count = len(self.stock_pool)
            self.stock_pool = new_monitor_list
            
            # 输出变化信息
            if added or removed:
                logger.info(f"📊 监控范围更新: {old_count}只 → {len(self.stock_pool)}只")
                if added:
                    logger.info(f"   新增监控: {', '.join(added)}")
                if removed:
                    logger.info(f"   移除监控: {', '.join(removed)}")
            
            return True
        except Exception as e:
            logger.error(f"更新监控范围失败: {str(e)}")
            return False

    def _validate_stocks(self, stock_list):
        """验证股票列表，返回有效的股票"""
        valid_stocks = []
        
        for stock in stock_list:
            try:
                # 检查股票代码格式
                if not stock or len(stock) < 6:
                    logger.debug(f"股票代码格式无效: {stock}")
                    continue
                
                # 检查股票是否存在
                stock_info = xtdata.get_instrument_detail(stock)
                if not stock_info:
                    logger.debug(f"股票不存在: {stock}")
                    continue
                
                # 检查股票是否可交易
                if stock_info.get('IsTrading', False) == False:
                    logger.debug(f"股票不可交易: {stock}")
                    continue
                
                # 检查是否有基本的历史数据
                try:
                    # 获取最近5天的数据来验证（使用与回测系统一致的复权方式）
                    from config_loader import BACKTEST_CONFIG
                    dividend_type = BACKTEST_CONFIG.get('dividend_type', 'front')
                    recent_data = xtdata.get_market_data(
                        stock_list=[stock],
                        period='1d',
                        start_time=(datetime.now() - timedelta(days=10)).strftime('%Y%m%d'),
                        end_time=datetime.now().strftime('%Y%m%d'),
                        count=5,
                        dividend_type=dividend_type,
                        fill_data=True
                    )
                    
                    if recent_data and len(recent_data) > 0:
                        valid_stocks.append(stock)
                        logger.debug(f"股票有效: {stock}")
                    else:
                        logger.debug(f"股票无历史数据: {stock}")
                        
                except Exception as e:
                    logger.debug(f"验证股票 {stock} 数据时出错: {str(e)}")
                    continue
                    
            except Exception as e:
                logger.debug(f"验证股票 {stock} 时出错: {str(e)}")
                continue
        
        logger.info(f"股票验证完成: {len(valid_stocks)}/{len(stock_list)} 只股票有效")
        return valid_stocks

    def _get_stock_basic_info(self, stocks_to_process):
        """获取股票基本信息"""
        # 用于存储没有足够历史数据的股票
        insufficient_data_stocks = []

        # 获取股票基本信息
        for stock in stocks_to_process:
            # 确保股票代码格式统一
            stock = stock.upper()

            try:
                # 获取流通股本（单位：股）
                value, source = self.get_outstanding_shares(stock)

                # 获取量比（优先从API，否则手动计算）
                full_tick_data = xtdata.get_full_tick([stock])
                quote = full_tick_data.get(stock, {}) if isinstance(full_tick_data, dict) else {}
                
                # 尝试多种可能的量比字段名
                volume_ratio = None
                for ratio_field in ['volRatio', 'volumeRatio', 'volume_ratio', 'turnoverRatio']:
                    if ratio_field in quote and quote[ratio_field] is not None and quote[ratio_field] > 0:
                        volume_ratio = quote[ratio_field]
                        break
                
                # 如果没有直接的量比字段，尝试手动计算
                if volume_ratio is None or volume_ratio <= 0:
                    current_volume = quote.get('volume', 0)
                    previous_volume = quote.get('pvolume', 0)
                    if isinstance(previous_volume, (int, float)) and previous_volume > 0:
                        volume_ratio = current_volume / previous_volume
                    else:
                        volume_ratio = 0

                self.float_shares[stock] = value
                self.float_shares_source[stock] = source

                # 检查是否有足够的历史数据
                hist_np = self.hist_np_cache.get(stock)
                if hist_np is not None:
                    closes = hist_np.get('close')
                    if closes is not None:
                        # 检查是否有足够的数据进行计算（更新为60个交易日）
                        required_days = 60  # 计算60日价格区间需要至少60个交易日
                        actual_days = len(closes)
                        if actual_days < required_days:
                            insufficient_data_stocks.append(stock)
                            logger.warning(f"{stock} 上市时间较短，仅有 {actual_days} 个交易日数据，少于所需 {required_days} 个交易日，将不纳入监控")
            except Exception as e:
                # 获取失败标记为不可用，不使用任何默认值
                self.float_shares[stock] = 0
                self.float_shares_source[stock] = 'unavailable'
                logger.warning(f"{stock} 流通股本获取失败，标记为不可用，不参与换手率过滤")
                logger.debug(f"错误详情: {str(e)}")

            # 初始化当日数据（无论流通股本获取是否成功）
            self.today_data[stock] = {
                'open': 0,
                'high': 0,
                'low': 0,
                'last': 0,
                'volume': 0,
                'timestamp': 0
            }

        # 从监控列表中移除没有足够历史数据的股票
        if insufficient_data_stocks:
            logger.info(f"从监控列表中移除 {len(insufficient_data_stocks)} 只新股: {insufficient_data_stocks}")
            self.stock_pool = [stock for stock in self.stock_pool if stock not in insufficient_data_stocks]
            
            # 同时从相关数据结构中移除
            for stock in insufficient_data_stocks:
                if stock in self.float_shares:
                    del self.float_shares[stock]
                if stock in self.float_shares_source:
                    del self.float_shares_source[stock]
                if stock in self.today_data:
                    del self.today_data[stock]
                if stock in self.hist_data:
                    del self.hist_data[stock]
                if stock in self.hist_np_cache:
                    del self.hist_np_cache[stock]

        logger.info("数据初始化完成")
        logger.info(f"最终监控股票池共 {len(self.stock_pool)} 只: {self.stock_pool}")

    def get_outstanding_shares(self, stock, suppress_warnings=False):
        """获取股票流通股本（股）- 支持多字段容错"""
        try:
            # 1. 首选：合约信息中的流通股本字段
            detail = xtdata.get_instrument_detail(stock)
            if isinstance(detail, dict):
                # 根据API文档，流通股本字段可能的名称（按优先级排序）
                candidate_keys = [
                    'FloatVolume',      # API文档中提到的标准字段名
                    'float_volume',     # 小写版本
                    'FloatShare',       # 流通股份
                    'float_share',      # 小写版本
                    'CirculatingCapital', # 流通股本
                    'circulating_capital',
                    'TradableShares',   # 可交易股份
                    'tradable_shares'
                ]

                for key in candidate_keys:
                    if key in detail:
                        try:
                            value = detail[key]
                            # 确保value不是列表或元组
                            if isinstance(value, (list, tuple)):
                                if len(value) > 0:
                                    value = value[0]  # 取第一个元素
                                else:
                                    continue  # 跳过空列表
                            
                            # 确保value是字符串或数字
                            if not isinstance(value, (str, int, float)):
                                value = str(value)
                            
                            # 安全地转换为数字
                            float_value = float(str(value))
                            numeric_value = int(float_value)
                            
                            if numeric_value > 0:
                                if not suppress_warnings:
                                    logger.info(f"{stock} 使用合约信息字段 {key}={numeric_value:,} 股 作为流通股本")
                                return numeric_value, 'instrument'
                            else:
                                if not suppress_warnings:
                                    logger.warning(f"{stock} 合约信息字段 {key} 返回为0，尝试其它来源")
                        except Exception as _:
                            if not suppress_warnings:
                                logger.warning(f"{stock} 解析字段 {key} 失败，值= {detail.get(key)}，尝试其它来源")

                # 科创板特殊处理：FloatVolume为0时使用TotalVolume作为替代
                if 'TotalVolume' in detail:
                    try:
                        total_value = detail['TotalVolume']
                        numeric_total = int(float(total_value))
                        if numeric_total > 0:
                            # 判断是否是科创板（688开头）
                            if stock.startswith('688.'):
                                if not suppress_warnings:
                                    logger.info(f"{stock} 科创板FloatVolume缺失，使用TotalVolume={numeric_total:,} 股 作为流通股本")
                                return numeric_total, 'instrument_total'
                            # 非科创板但FloatVolume为0，也使用TotalVolume
                            float_vol = detail.get('FloatVolume', 0)
                            if float_vol == 0:
                                if not suppress_warnings:
                                    logger.info(f"{stock} FloatVolume为0，使用TotalVolume={numeric_total:,} 股 作为流通股本")
                                return numeric_total, 'instrument_total'
                    except Exception as _:
                        if not suppress_warnings:
                            logger.warning(f"{stock} 解析TotalVolume失败，值= {detail.get('TotalVolume')}")

                # 若合约信息未获得正值，尝试财务数据接口
                try:
                    # 尝试从财务数据获取流通股本
                    financial_fields = ['float_a_shares', 'FloatAShares', 'circulating_shares']
                    for field in financial_fields:
                        try:
                            financial_data = xtdata.get_financial_data([stock], [field])
                            if isinstance(financial_data, dict) and stock in financial_data:
                                field_data = financial_data[stock]
                                if hasattr(field_data, 'iloc') and len(field_data) > 0:
                                    # DataFrame格式
                                    value = field_data.iloc[-1]  # 取最新数据
                                elif isinstance(field_data, (list, tuple)) and len(field_data) > 0:
                                    value = field_data[-1]
                                else:
                                    value = field_data
                                
                                if value is not None:
                                    # 安全地转换为数字
                                    try:
                                        # 确保value是字符串或数字
                                        if not isinstance(value, (str, int, float)):
                                            value = str(value)
                                        
                                        float_value = float(str(value))
                                        numeric_value = int(float_value)
                                        
                                        if numeric_value > 0:
                                            if not suppress_warnings:
                                                logger.info(f"{stock} 使用财务数据字段 {field}={numeric_value:,} 股 作为流通股本")
                                            return numeric_value, 'financial'
                                    except (ValueError, TypeError) as convert_error:
                                        if not suppress_warnings:
                                            logger.warning(f"{stock} 财务数据字段 {field} 值 {value} 转换失败: {convert_error}")
                        except Exception:
                            continue
                except Exception as inner_e:
                    if not suppress_warnings:
                        logger.debug(f"{stock} get_financial_data 调用异常: {str(inner_e)}")

                # 未取到则返回不可用标记
                if not suppress_warnings:
                    logger.warning(f"{stock} 未从合约/财务中取得有效流通股本，标记为不可用")
                return 0, 'unavailable'
            else:
                if not suppress_warnings:
                    logger.warning(f"{stock} 获取 instrument detail 失败，标记为不可用")
                return 0, 'unavailable'
        except Exception as e:
            # 任意异常直接返回硬编码默认值，避免策略中断
            if not suppress_warnings:
                logger.warning(f"{stock} 获取流通股本异常({str(e)}), 标记为不可用")
            return 0, 'unavailable'

    def get_stock_market_cap(self, stock: str) -> float:
        """
        获取股票流通市值（单位：亿元）
        
        :param stock: 股票代码
        :return: 流通市值（亿元），获取失败返回0
        """
        try:
            # 获取流通股本（股）
            shares, source = self.get_outstanding_shares(stock, suppress_warnings=True)
            if shares <= 0:
                return 0.0
            
            # 获取当前价格
            current_price = self.today_data.get(stock, {}).get('last', 0)
            if current_price <= 0:
                return 0.0
            
            # 计算流通市值（亿元） = 流通股本（股） × 最新价（元） / 1亿
            market_cap = shares * current_price / 100000000
            
            logger.debug(f"{stock} 流通市值: {market_cap:.2f}亿元 (流通股本: {shares:,}股, 价格: {current_price:.2f}元)")
            return market_cap
            
        except Exception as e:
            logger.debug(f"{stock} 获取流通市值失败: {str(e)}")
            return 0.0
    
    def get_stock_type(self, stock: str) -> str:
        """
        判断股票类型（大中小盘）
        
        分类标准：
        - 大盘股：流通市值 > 200亿元
        - 中盘股：流通市值 50-200亿元
        - 小盘股：流通市值 < 50亿元
        
        :param stock: 股票代码
        :return: 'large'(大盘), 'mid'(中盘), 'small'(小盘), 'unknown'(未知)
        """
        market_cap = self.get_stock_market_cap(stock)
        
        if market_cap <= 0:
            logger.debug(f"{stock} 流通市值为0，无法判断类型")
            return 'unknown'
        
        thresholds = self.sell_params['market_cap_threshold']
        
        if market_cap > thresholds['large']:
            stock_type = 'large'
            type_name = '大盘股'
        elif market_cap >= thresholds['small']:
            stock_type = 'mid'
            type_name = '中盘股'
        else:
            stock_type = 'small'
            type_name = '小盘股'
        
        logger.debug(f"{stock} 流通市值: {market_cap:.2f}亿元, 类型: {type_name}")
        return stock_type

    def download_target_stocks_data(self, target_stocks):
        """下载指定股票的历史数据 - 使用增量下载优化"""
        logger.info(f"开始下载指定股票 {target_stocks} 的历史数据...")
        
        # 自动计算所需历史数据天数（考虑动态量能阈值配置）
        try:
            from config_loader import get_config_loader
            config_loader = get_config_loader()
            config = config_loader._config  # 获取原始配置字典
            from volume_validators.dynamic_threshold import DynamicVolumeThresholdCalculator
            required_days = DynamicVolumeThresholdCalculator.get_required_history_days(config)
        except Exception as e:
            logger.warning(f"无法加载动态阈值配置，使用默认180天: {e}")
            required_days = 180
        
        # 设置时间范围 - 自动扩展以满足动态量能阈值需求
        today = datetime.now().strftime('%Y%m%d')
        self.end_date = (datetime.now() - timedelta(days=1)).strftime('%Y%m%d')
        self.start_date = (datetime.now() - timedelta(days=required_days)).strftime('%Y%m%d')
        logger.info(f"数据下载范围: {self.start_date} ~ {self.end_date} (自动扩展{required_days}天以满足动态量能阈值需求)")
        
        # 批量下载日线数据（使用 config.yaml 配置）
        # 注：download_history_data 使用QMT客户端配置的默认复权方式
        # 建议在QMT客户端中设置默认复权方式为：不复权（实盘/模拟盘）或前复权（回测）
        download_mode = "增量" if self.incremental_download else "全量"
        try:
            logger.info(f"批量下载日线数据（{download_mode}）...")
            start_time = '' if self.incremental_download else self.start_date
            xtdata.download_history_data(
                target_stocks, 
                '1d', 
                start_time, 
                self.end_date, 
                incrementally=self.incremental_download
            )
            logger.info(f"已请求批量{download_mode}下载 {len(target_stocks)} 只股票的日线数据")
        except Exception as e:
            logger.warning(f"批量下载失败，尝试逐个下载: {str(e)}")
            # 如果批量下载失败，逐个下载
            for stock in target_stocks:
                try:
                    stock_info = xtdata.get_instrument_detail(stock)
                    display_name = f"{stock_info.get('InstrumentName', stock)}" if stock_info else stock
                    
                    logger.info(f"正在下载 {display_name} 的日线数据...")
                    start_time = '' if self.incremental_download else self.start_date
                    xtdata.download_history_data(stock, '1d', start_time, self.end_date, incrementally=self.incremental_download)
                    logger.info(f"已请求{download_mode}下载 {display_name} 的日线数据")
                    
                except Exception as e:
                    logger.error(f"下载 {stock} 日线数据时出错: {str(e)}")
        
        # 下载分钟级数据（使用扩展后的起始日期，与日线数据保持一致）
        try:
            # 分钟数据也使用相同的起始日期，确保有足够历史数据用于动态阈值计算
            minute_start_date = self.start_date  # 使用与日线数据相同的起始日期
            logger.info(f"批量下载分钟级数据（{download_mode}）...")
            start_time = '' if self.incremental_download else minute_start_date
            xtdata.download_history_data(
                target_stocks, 
                '1m', 
                start_time, 
                self.end_date, 
                incrementally=self.incremental_download
            )
            logger.info(f"已请求批量{download_mode}下载 {len(target_stocks)} 只股票的分钟级数据")
        except Exception as e:
            logger.warning(f"批量下载分钟级数据失败，尝试逐个下载: {str(e)}")
            # 如果批量下载失败，逐个下载
            for stock in target_stocks:
                try:
                    stock_info = xtdata.get_instrument_detail(stock)
                    display_name = f"{stock_info.get('InstrumentName', stock)}" if stock_info else stock
                    
                    logger.info(f"正在下载 {display_name} 的分钟级数据...")
                    start_time = '' if self.incremental_download else minute_start_date
                    xtdata.download_history_data(stock, '1m', start_time, self.end_date, incrementally=self.incremental_download)
                    logger.info(f"已请求{download_mode}下载 {display_name} 的分钟级数据")
                    
                except Exception as e:
                    logger.error(f"下载 {stock} 分钟级数据时出错: {str(e)}")
        
        logger.info(f"指定股票数据下载完成: {len(target_stocks)} 只股票")

    def check_and_download_data(self, target_stocks):
        """检查本地数据并按需下载"""
        if not target_stocks:
            logger.info("没有目标股票，跳过数据检查")
            return
            
        logger.info(f"开始检查本地数据可用性: {len(target_stocks)} 只股票")
        
        # 设置时间范围
        today = datetime.now().strftime('%Y%m%d')
        end_date = (datetime.now() - timedelta(days=1)).strftime('%Y%m%d')
        start_date = (datetime.now() - timedelta(days=180)).strftime('%Y%m%d')
        
        # 检查每只股票的本地数据
        stocks_need_download = []
        
        for stock in target_stocks:
            try:
                # 检查本地是否有足够的历史数据
                has_sufficient_data = self._check_stock_local_data(stock, start_date, end_date)
                
                if not has_sufficient_data:
                    stocks_need_download.append(stock)
                    logger.info(f"{stock}: 本地数据不足，需要下载")
                else:
                    logger.info(f"{stock}: 本地数据充足")
                    
            except Exception as e:
                logger.warning(f"检查 {stock} 本地数据时出错: {str(e)}")
                stocks_need_download.append(stock)
        
        # 只下载需要下载的股票数据
        if stocks_need_download:
            logger.info(f"开始下载 {len(stocks_need_download)} 只股票的数据...")
            self.download_target_stocks_data(stocks_need_download)
        else:
            logger.info("所有股票本地数据充足，无需下载")

    def _check_stock_local_data(self, stock, start_date, end_date):
        """检查单只股票的本地数据是否充足 - 使用增量下载优化"""
        try:
            # 获取配置的复权方式（与回测系统保持一致）
            from config_loader import BACKTEST_CONFIG
            dividend_type = BACKTEST_CONFIG.get('dividend_type', 'front')
            
            # 检查日线数据 - 使用更精确的检查
            daily_data = xtdata.get_market_data(
                stock_list=[stock],
                period='1d',
                start_time=start_date,
                end_time=end_date,
                count=-1,
                dividend_type=dividend_type,
                fill_data=True
            )
            
            # 检查数据质量和数量
            if not daily_data or len(daily_data) < 60:  # 至少需要60个交易日的数据
                logger.debug(f"{stock}: 日线数据不足 ({len(daily_data) if daily_data else 0} 条)")
                return False
            
            # 检查数据完整性 - 确保没有大量缺失数据
            if hasattr(daily_data, 'close'):
                close_prices = daily_data['close']
                if close_prices.isnull().sum() > len(close_prices) * 0.1:  # 缺失数据不超过10%
                    logger.debug(f"{stock}: 日线数据质量不佳，缺失数据过多")
                    return False
            
            # 检查分钟级数据（最近30天）
            recent_start = (datetime.now() - timedelta(days=30)).strftime('%Y%m%d')
            minute_data = xtdata.get_market_data(
                stock_list=[stock],
                period='1m',
                start_time=recent_start,
                end_time=end_date,
                count=-1,
                dividend_type=dividend_type,  # 使用与日线相同的复权方式
                fill_data=True
            )
            
            # 检查分钟级数据质量
            if not minute_data or len(minute_data) < 100:  # 至少需要100条分钟数据
                logger.debug(f"{stock}: 分钟级数据不足 ({len(minute_data) if minute_data else 0} 条)")
                return False
            
            # 检查分钟级数据完整性
            if hasattr(minute_data, 'close'):
                close_prices = minute_data['close']
                if close_prices.isnull().sum() > len(close_prices) * 0.05:  # 缺失数据不超过5%
                    logger.debug(f"{stock}: 分钟级数据质量不佳，缺失数据过多")
                    return False
                
            logger.debug(f"{stock}: 本地数据充足 - 日线:{len(daily_data)}条, 分钟级:{len(minute_data)}条")
            return True
            
        except Exception as e:
            logger.debug(f"检查 {stock} 本地数据时出错: {str(e)}")
            return False

    def download_history_data(self):
        """下载历史K线数据 - 使用 config.yaml 配置"""
        download_mode = "增量" if self.incremental_download else "全量"
        logger.info(f"开始下载历史K线数据（{download_mode}）...")

        # 使用交易日接口获取最近的交易日，避免因节假日导致的数据不足
        today = datetime.now().strftime('%Y%m%d')
        
        # 默认使用自然日计算（扩展到90天以确保有足够60个交易日）
        self.end_date = (datetime.now() - timedelta(days=1)).strftime('%Y%m%d')
        self.start_date = (datetime.now() - timedelta(days=90)).strftime('%Y%m%d')
        
        logger.info(f"下载时间范围: {self.start_date} 到 {self.end_date}")
        
        # 获取交易日列表（扩展到100天以确保有足够60个交易日）
        try:
            # 正确的调用方式: get_trading_dates(market, start_date, end_date)
            # 获取过去100天内的交易日（确保能获得至少60个交易日）
            start_date = (datetime.now() - timedelta(days=100)).strftime('%Y%m%d')
            trading_dates = xtdata.get_trading_calendar('SH', start_date, today)
            if trading_dates and len(trading_dates) >= 60:
                # 取最近的60个交易日
                recent_trading_dates = trading_dates[-60:]
                
                # 根据当前时间决定是否包含今天
                now = datetime.now()
                today_int = int(today)
                
                # 如果是交易日且在交易时间内，则不包含今天的数据
                # 如果是非交易日或在交易时间外，则可以包含今天的数据（如果有）
                is_trading_day = today_int in trading_dates
                is_trading_time = (9 <= now.hour <= 15) if is_trading_day else False
                
                if is_trading_day and is_trading_time:
                    # 交易日的交易时间内：不包含今天
                    filtered_dates = [d for d in recent_trading_dates if d < today_int]
                    logger.info(f"交易时间内，不包含今天数据")
                else:
                    # 非交易时间或非交易日：包含今天（如果有的话）
                    filtered_dates = [d for d in recent_trading_dates if d <= today_int]
                    logger.info(f"非交易时间，包含今天数据（如果有）")
                    
                if filtered_dates:
                    self.end_date = str(filtered_dates[-1])  # 最近一个交易日
                    self.start_date = str(filtered_dates[0]) if len(filtered_dates) >= 120 else str(filtered_dates[0])
        except Exception as e:
            logger.warning(f"获取交易日列表失败: {e}，使用默认日期计算方式")

        logger.info(f"下载历史数据时间范围: {self.start_date} 到 {self.end_date}")

        # 逐个下载股票数据（使用 config.yaml 配置）
        logger.info(f"正在进行{download_mode}数据下载...")
        for stock in self.stock_pool:
            try:
                # 确保股票代码格式统一
                stock = stock.upper()
                
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                # 构建显示名称
                display_name = f"{stock} ({stock_name})" if stock_name else stock

                # 根据配置选择下载方式
                start_time = '' if self.incremental_download else self.start_date
                xtdata.download_history_data(stock, '1d', start_time, self.end_date, incrementally=self.incremental_download)
                logger.info(f"已请求{download_mode}下载 {display_name} 的历史数据")
            except Exception as e:
                # 获取股票名称（异常情况下）
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                # 构建显示名称
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                logger.error(f"增量下载 {display_name} 历史数据时出错: {str(e)}")

        # 等待数据下载完成
        logger.info("等待历史数据下载...")
        time.sleep(8)  # 增加等待时间确保下载完成

        # 获取历史K线数据
        for stock in self.stock_pool:
            try:
                # 确保股票代码格式统一
                stock = stock.upper()

                # 使用get_local_data获取已下载的数据（应用复权）
                from config_loader import BACKTEST_CONFIG
                dividend_type = BACKTEST_CONFIG.get('dividend_type', 'front')
                klines = xtdata.get_local_data(
                    stock_list=[stock],
                    period='1d',
                    start_time=self.start_date,
                    end_time=self.end_date,
                    dividend_type=dividend_type  # ✅ 应用复权方式
                )

                if klines and stock in klines:
                    # 存储历史数据（原始）
                    self.hist_data[stock] = klines[stock]
                    # 构建numpy缓存
                    try:
                        close_arr = np.array(self._first_non_empty(self.hist_data[stock], 'close', 'Close', 'CLOSE', 'last_close', 'lastClose'))
                        volume_arr = np.array(self._first_non_empty(self.hist_data[stock], 'volume', 'Volume', 'VOLUME', 'vol'))
                        # time 优先列，否则使用索引
                        last_time = self._last_time_str(self.hist_data[stock])  # 触发内部路径
                        # 我们仍需全量时间数组
                        if isinstance(self.hist_data[stock], dict):
                            time_list = self._to_list_safe(self.hist_data[stock], 'time')
                        else:
                            if 'time' in self.hist_data[stock]:
                                time_list = self._to_list_safe(self.hist_data[stock], 'time')
                            elif 'Time' in self.hist_data[stock]:
                                time_list = self._to_list_safe(self.hist_data[stock], 'Time')
                            else:
                                try:
                                    idx = getattr(self.hist_data[stock], 'index', [])
                                    # 更安全的处理方式，避免类型检查问题
                                    if hasattr(idx, 'tolist') and callable(getattr(idx, 'tolist', None)):
                                        # 确保idx是numpy数组
                                        if not isinstance(idx, np.ndarray):
                                            idx = np.array(list(idx) if hasattr(idx, '__iter__') else [])
                                        time_list = idx.tolist()
                                    else:
                                        # 确保idx是可迭代的
                                        time_list = list(idx) if hasattr(idx, '__iter__') else []
                                except Exception:
                                    time_list = []
                        time_arr = np.array(time_list)
                        self.hist_np_cache[stock] = {
                            'close': close_arr,
                            'volume': volume_arr,
                            'time': time_arr
                        }
                        
                        # 记录实际获取到的交易日数量
                        actual_trading_days = len(close_arr) if len(close_arr) > 0 else 0
                        # 获取股票名称
                        stock_name = ""
                        try:
                            detail = xtdata.get_instrument_detail(stock)
                            if isinstance(detail, dict):
                                stock_name = detail.get('InstrumentName', '')
                        except:
                            pass
                        
                        # 构建显示名称
                        display_name = f"{stock} ({stock_name})" if stock_name else stock
                        logger.info(f"已加载 {display_name} 的K线数据 ({actual_trading_days}个交易日)")
                    except Exception as e:
                        logger.debug(f"构建numpy缓存失败 {stock}: {str(e)}")
                        # 获取股票名称
                        stock_name = ""
                        try:
                            detail = xtdata.get_instrument_detail(stock)
                            if isinstance(detail, dict):
                                stock_name = detail.get('InstrumentName', '')
                        except:
                            pass
                        
                        # 构建显示名称
                        display_name = f"{stock} ({stock_name})" if stock_name else stock
                        logger.info(f"已加载 {display_name} 的K线数据")
                else:
                    # 获取股票名称
                    stock_name = ""
                    try:
                        detail = xtdata.get_instrument_detail(stock)
                        if isinstance(detail, dict):
                            stock_name = detail.get('InstrumentName', '')
                    except:
                        pass
                    
                    # 构建显示名称
                    display_name = f"{stock} ({stock_name})" if stock_name else stock
                    logger.warning(f"{display_name} 的K线数据加载失败")
            except Exception as e:
                # 获取股票名称
                stock_name = ""
                try:
                    detail = xtdata.get_instrument_detail(stock)
                    if isinstance(detail, dict):
                        stock_name = detail.get('InstrumentName', '')
                except:
                    pass
                
                # 构建显示名称
                display_name = f"{stock} ({stock_name})" if stock_name else stock
                logger.error(f"获取 {display_name} 历史数据时出错: {str(e)}")

    def subscribe_realtime_quotes(self):
        """使用官方推荐的subscribe_whole_quote方式订阅行情"""
        logger.info("正在订阅实时行情...")

        try:
            # 创建一个包装函数来处理回调
            def quote_callback_wrapper(data):
                self.quote_callback(data)

            # 订阅整个股票池
            xtdata.subscribe_whole_quote(self.stock_pool, quote_callback_wrapper)
            logger.info(f"成功订阅 {len(self.stock_pool)} 只股票的实时行情")
        except Exception as e:
            logger.error(f"订阅失败: {str(e)}")
            # 尝试逐个订阅
            for stock in self.stock_pool:
                try:
                    def single_quote_callback_wrapper(data):
                        self.quote_callback(data)

                    xtdata.subscribe_whole_quote([stock], single_quote_callback_wrapper)
                    logger.info(f"成功订阅 {stock} 的实时行情")
                except Exception as ex:
                    logger.error(f"订阅 {stock} 失败: {str(ex)}")

    def initial_screen(self):
        """启动后对全池进行一次筛选：抓取快照，更新today_data并复用现有条件判断"""
        logger.info("开始启动全池一次性筛选...")
        
        # 注意：数据完整性检查已在 initialize_data 中完成，这里不再重复
        # 获取一份全量快照（若接口不可用，可改为逐只 get_full_tick 或 get_last_tick）
        try:
            # 按文档，get_full_tick 接收股票列表并返回 {code: quote}
            snapshots = xtdata.get_full_tick(self.stock_pool)
        except Exception as e:
            logger.warning(f"获取快照失败，降级为逐只抓取: {str(e)}")
            snapshots = {}
            for code in self.stock_pool:
                try:
                    res = xtdata.get_full_tick([code])
                    if isinstance(res, dict) and code in res:
                        snapshots[code] = res[code]
                except Exception:
                    pass

        # 遍历更新今日数据并执行条件检查
        for stock_code in self.stock_pool:
            try:
                # 检查是否已触发，避免重复触发
                if stock_code in self.buy_stocks:
                    continue
                    
                snap = snapshots.get(stock_code, {}) if isinstance(snapshots, dict) else {}
                open_price = snap.get('open', 0)
                high_price = snap.get('high', 0)
                low_price = snap.get('low', 0)
                last_price = snap.get('last', snap.get('lastPrice', 0))
                volume = snap.get('volume', 0)
                last_close = snap.get('lastClose', snap.get('preClose', 0))
                # 换手率字段可能存在别名，做多键兼容
                turnover_rate_rt = None
                for k in ['turnoverRate', 'turnover_rate', 'turnover', 'turnrate']:
                    if k in snap:
                        turnover_rate_rt = snap.get(k)
                        break
                # 量比/前一指标等
                vol_ratio = snap.get('volRatio', None)
                pvolume = snap.get('pvolume', None)

                # 初始化/更新今日数据
                self.today_data[stock_code] = {
                    'open': open_price,
                    'high': high_price,
                    'low': low_price,
                    'last': last_price,
                    'volume': volume,
                    'lastClose': last_close,
                    'turnoverRateRT': turnover_rate_rt,
                    'volRatio': vol_ratio,
                    'pvolume': pvolume,
                    'timestamp': time.time()
                }

                # 复用现有检查逻辑
                if self.check_conditions(stock_code):
                    # 添加到已触发列表，避免重复触发
                    pass
            except Exception as e:
                logger.debug(f"启动筛选处理 {stock_code} 异常: {str(e)}")

        logger.info("启动全池一次性筛选完成")

    def quote_callback(self, data):
        """新版行情回调处理函数 - 带锁保护"""
        with self.callback_lock:
            try:
                # 边界检查：确保data是字典
                if not isinstance(data, dict) or not data:
                    logger.debug("收到的行情数据为空或非字典类型，跳过处理")
                    return

                # 边界检查：确保监控列表不为空
                if not isinstance(self.stock_pool, list) or not self.stock_pool:
                    logger.warning("监控列表为空或非列表类型，无法处理行情数据")
                    return

                # 根据xtQuant文档，data应该是字典，键是股票代码，值是行情数据
                for stock_code, quote_data in data.items():
                    # 统一股票代码格式（大写）
                    stock_code = stock_code.upper()

                    # 安全检查：确保股票在股票池中
                    if stock_code not in self.stock_pool:
                        logger.debug(f"收到非股票池股票: {stock_code}")
                        continue

                    # 检查是否已触发，避免重复触发
                    if stock_code in self.buy_stocks:
                        continue

                    # 检查市场状态
                    if not self.check_market_status():
                        continue

                    # 安全检查：确保股票已初始化
                    if stock_code not in self.today_data:
                        logger.error(f"股票 {stock_code} 未在今日数据中初始化")
                        # 尝试初始化
                        self.today_data[stock_code] = {
                            'open': 0,
                            'high': 0,
                            'low': 0,
                            'last': 0,
                            'volume': 0,
                            'timestamp': 0
                        }
                        logger.warning(f"已为 {stock_code} 创建临时今日数据")

                    # 提取行情字段 - 使用xtQuant文档中的字段名
                    # 提取API字段，不做本地计算
                    open_price = quote_data.get('open', 0)
                    high_price = quote_data.get('high', 0)
                    low_price = quote_data.get('low', 0)
                    last_price = quote_data.get('last', quote_data.get('lastPrice', 0))
                    volume = quote_data.get('volume', 0)
                    last_close = quote_data.get('lastClose', quote_data.get('preClose', 0))
                    # 换手率字段可能存在别名，做多键兼容
                    turnover_rate_rt = None
                    for k in ['turnoverRate', 'turnover_rate', 'turnover', 'turnrate']:
                        if k in quote_data:
                            turnover_rate_rt = quote_data.get(k)
                            break
                    # 量比计算 - 使用新的量比计算器（连续量比）
                    vol_ratio = None
                    
                    # 1. 尝试直接获取量比字段（多种可能的字段名）
                    for ratio_field in ['volRatio', 'volumeRatio', 'volume_ratio', 'turnoverRatio']:
                        if ratio_field in quote_data and quote_data[ratio_field] is not None:
                            vol_ratio = quote_data[ratio_field]
                            break
                    
                    # 2. 使用新的量比计算器计算连续量比
                    if (vol_ratio is None or vol_ratio <= 0) and self.volume_ratio_calculator:
                        try:
                            from datetime import datetime
                            import pandas as pd
                            
                            # 获取当前时间
                            time_tag = quote_data.get('timetag', '')
                            current_time = datetime.now()
                            if time_tag:
                                try:
                                    time_parts = time_tag.split(' ')
                                    if len(time_parts) >= 2:
                                        time_str = time_parts[1]
                                        hour, minute, second = map(int, time_str.split(':')[:3])
                                        current_time = current_time.replace(hour=hour, minute=minute, second=second)
                                except:
                                    pass
                            
                            # 获取当前累计成交量
                            current_vol = quote_data.get('volume', 0)
                            if current_vol > 0 and current_vol < 1000:
                                current_vol = current_vol * 10000 / 100
                            
                            # 获取历史数据
                            hist_np = self.hist_np_cache.get(stock_code)
                            if hist_np is not None and 'volume' in hist_np:
                                historical_daily_data = pd.DataFrame({
                                    'volume': hist_np['volume']
                                })
                                
                                # 计算固定窗口实时量比（累计量比）
                                current_vol = quote_data.get('volume', 0)
                                time_tag = quote_data.get('timetag', '')
                                if not time_tag and current_time:
                                    time_tag = current_time.strftime('%Y-%m-%d %H:%M:%S')
                                
                                vol_ratio = self.volume_ratio_calculator.calculate_cumulative_volume_ratio(
                                    stock_code,
                                    current_volume=float(current_vol) if current_vol else 0,
                                    historical_daily_data=historical_daily_data,
                                    time_tag=time_tag
                                )
                                
                        except Exception as e:
                            logger.debug(f"{stock_code} 使用新量比计算器计算失败: {e}，使用旧方法")
                    
                    # 3. 如果没有直接的量比字段且新计算器失败，尝试旧方法
                    if vol_ratio is None or vol_ratio <= 0:
                        current_vol = quote_data.get('volume', 0)
                        previous_vol = quote_data.get('pvolume', 0)
                        if isinstance(previous_vol, (int, float)) and previous_vol > 0:
                            vol_ratio = current_vol / previous_vol
                    
                    pvolume = quote_data.get('pvolume', None)
                    ma5 = quote_data.get('ma5', None)
                    ma10 = quote_data.get('ma10', None)
                    ma20 = quote_data.get('ma20', None)
                    pct_chg = quote_data.get('pctChg', quote_data.get('changePercent', None))

                    # 更新当日数据
                    if self.today_data[stock_code]['open'] == 0:
                        # 第一次收到行情，初始化当日数据
                        self.today_data[stock_code] = {
                            'open': open_price,
                            'high': high_price,
                            'low': low_price,
                            'last': last_price,
                            'volume': volume,
                            'lastClose': last_close,
                            'turnoverRateRT': turnover_rate_rt,
                            'volRatio': vol_ratio,
                            'pvolume': pvolume,
                            'ma5': ma5,
                            'ma10': ma10,
                            'ma20': ma20,
                            'pctChg': pct_chg,
                            'timestamp': time.time()
                        }
                    else:
                        # 更新当日数据
                        self.today_data[stock_code]['high'] = max(self.today_data[stock_code]['high'], high_price)
                        self.today_data[stock_code]['low'] = min(self.today_data[stock_code]['low'], low_price)
                        self.today_data[stock_code]['last'] = last_price
                        self.today_data[stock_code]['volume'] = volume
                        # 添加时间戳，用于日志打印
                        from datetime import datetime
                        self.today_data[stock_code]['time'] = datetime.now().strftime('%H:%M:%S')
                        self.today_data[stock_code]['lastClose'] = last_close
                        self.today_data[stock_code]['turnoverRateRT'] = turnover_rate_rt
                        if vol_ratio is not None:
                            self.today_data[stock_code]['volRatio'] = vol_ratio
                        if pvolume is not None:
                            self.today_data[stock_code]['pvolume'] = pvolume
                        if ma5 is not None:
                            self.today_data[stock_code]['ma5'] = ma5
                        if ma10 is not None:
                            self.today_data[stock_code]['ma10'] = ma10
                        if ma20 is not None:
                            self.today_data[stock_code]['ma20'] = ma20
                        if pct_chg is not None:
                            self.today_data[stock_code]['pctChg'] = pct_chg
                        self.today_data[stock_code]['timestamp'] = time.time()

                    # 统一预计算所有指标（避免check_conditions重复计算）
                    hist_np = self.hist_np_cache.get(stock_code)
                    if hist_np is not None:
                        try:
                            self.precalculate_indicators(stock_code, self.today_data[stock_code], hist_np)
                        except Exception as e:
                            logger.warning(f"{stock_code} 指标预计算失败: {e}")

                    # 检查是否满足条件
                    if self.check_conditions(stock_code):
                        # 添加到已触发列表，避免重复触发
                        pass
            except Exception as e:
                # 添加详细错误信息
                import traceback
                tb = traceback.format_exc()
                logger.error(f"处理行情回调时出错: {str(e)}\n{tb}")

    def check_market_status(self):
        """检查市场状态，确保在交易时间内"""
        try:
            # 检查时间（A股交易时间：9:30-11:30, 13:00-15:00）
            now = time.localtime()
            hour = now.tm_hour
            minute = now.tm_min

            # 上午交易时间
            if (hour == 9 and minute >= 30) or (hour == 10) or (hour == 11 and minute <= 30):
                return True

            # 下午交易时间
            if (hour == 13) or (hour == 14) or (hour == 15 and minute == 0):
                return True

            return False
        except:
            return False
    
    def _check_and_supplement_today_data(self, stock_list):
        """
        检查并补足当天的分钟级数据（如果程序在开盘后启动）
        
        检查逻辑：
        1. 检查当前是否在交易时间内
        2. 如果在交易时间内，检查滑动窗口是否有足够的数据
        3. 如果数据不足，通过 get_full_kline 获取当天的分钟级数据来补足
        
        Args:
            stock_list: 需要检查的股票列表
        """
        if not self.volume_ratio_calculator:
            return
        
        # 检查是否在交易时间内
        if not self.check_market_status():
            logger.debug("当前不在交易时间内，跳过当天数据检查")
            return
        
        try:
            from datetime import datetime
            import pandas as pd
            
            now = datetime.now()
            today = now.strftime('%Y%m%d')
            today_start = today + "000000"
            today_end = today + "235959"
            
            # 计算开盘时间（9:30）
            market_open = now.replace(hour=9, minute=30, second=0, microsecond=0)
            
            # 如果当前时间在开盘后，需要检查数据完整性
            if now < market_open:
                logger.debug("当前时间在开盘前，无需补足数据")
                return
            
            # 计算应该有的数据时间范围（从开盘到当前）
            expected_start = market_open
            expected_duration = (now - expected_start).total_seconds()
            
            # 如果开盘时间太短（少于1分钟），可能还没有数据
            if expected_duration < 60:
                logger.debug("开盘时间太短，可能还没有数据")
                return
            
            logger.info(f"检查当天数据完整性（开盘后启动检测）...")
            
            # 获取当天的分钟级数据
            minute_data_dict = xtdata.get_full_kline(
                field_list=['volume'],
                stock_list=stock_list,
                period='1m',
                start_time=today_start,
                end_time=today_end,
                count=-1
            )
            
            if not minute_data_dict or 'volume' not in minute_data_dict:
                logger.warning("无法获取当天分钟级数据，跳过数据补足")
                return
            
            volume_df = minute_data_dict['volume']
            supplemented_count = 0
            
            for stock_code in stock_list:
                try:
                    if stock_code not in volume_df.index:
                        continue
                    
                    stock_minute_data = volume_df.loc[stock_code]
                    if stock_minute_data.empty:
                        continue
                    
                    # 检查滑动窗口是否已有数据
                    has_window_data = False
                    if hasattr(self.volume_ratio_calculator, '_sliding_windows'):
                        window = self.volume_ratio_calculator._sliding_windows.get(stock_code)
                        if window and len(window) > 0:
                            # 检查窗口中最老的数据是否接近开盘时间
                            oldest_time = window[0][0] if window else None
                            if oldest_time and (now - oldest_time).total_seconds() >= expected_duration * 0.5:
                                has_window_data = True
                    
                    # 如果窗口数据不足，补足数据
                    if not has_window_data:
                        # 将分钟级数据转换为逐笔数据格式
                        tick_volumes = []
                        prev_volume = 0.0
                        for idx, vol in stock_minute_data.items():
                            if pd.notna(vol) and vol > 0:
                                # 计算增量成交量（当前累计 - 上一分钟累计）
                                incremental_vol = float(vol) - prev_volume
                                if incremental_vol > 0:
                                    # 将时间戳转换为datetime
                                    if isinstance(idx, pd.Timestamp):
                                        minute_time = idx.to_pydatetime()
                                    else:
                                        minute_time = pd.to_datetime(idx).to_pydatetime()
                                    
                                    # 只添加开盘后的数据
                                    if minute_time >= market_open:
                                        tick_volumes.append((minute_time, incremental_vol))
                                    prev_volume = float(vol)
                        
                        # 更新滑动窗口（按时间顺序添加）
                        if tick_volumes:
                            for tick_time, tick_vol in tick_volumes:
                                self.volume_ratio_calculator.update_sliding_window(
                                    stock_code, tick_time, tick_vol
                                )
                            
                            logger.info(f"{stock_code} 已补足当天数据，包含{len(tick_volumes)}条分钟数据（从{market_open.strftime('%H:%M:%S')}到{now.strftime('%H:%M:%S')}）")
                            supplemented_count += 1
                
                except Exception as e:
                    logger.debug(f"补足{stock_code}当天数据失败: {e}")
            
            if supplemented_count > 0:
                logger.info(f"已为{supplemented_count}只股票补足当天数据")
            else:
                logger.debug("所有股票的滑动窗口数据充足，无需补足")
        
        except Exception as e:
            logger.warning(f"检查并补足当天数据失败: {e}")

    def _extract_volume_strength(self, volume_price_result):
        """
        从量价分析结果中提取量能强度
        
        参数:
            volume_price_result: 量价分析结果字典
            
        返回:
            str: 'strong', 'moderate', 或 'normal'
        """
        pattern = volume_price_result.get('pattern', '')
        
        if '强烈放量' in pattern or '放量' in pattern:
            return 'strong'
        elif '温和放量' in pattern:
            return 'moderate'
        else:
            return 'normal'

    def check_conditions(self, stock):
        """
        策略路由器：根据价格位置选择合适的策略
        - 价格位置 <= 80%：使用低位标准策略
        - 价格位置 > 80%：使用高位强势策略
        """
        try:
            # 获取历史数据和当日数据
            hist_np = self.hist_np_cache.get(stock)
            today_data = self.today_data.get(stock)

            if hist_np is None or today_data is None:
                return False
            
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')

            if closes is None or volumes is None:
                logger.warning(f"{stock} 历史数据不足，无法计算条件")
                return False
                
            # 计算价格位置
            price_position_result = self._calculate_price_position(stock, today_data, closes)
            if not price_position_result['valid']:
                logger.debug(f"{stock} 价格位置计算失败: {price_position_result.get('reason')}")
                return False
            
            price_position = price_position_result['position']
            threshold = self.strategy_config['price_position_threshold']
            
            # 策略路由：根据价格位置选择策略
            if price_position <= threshold:
                # 低位策略（当前标准策略）
                logger.debug(f"{stock} 价格位置{price_position*100:.1f}% <= {threshold*100:.0f}%，使用低位标准策略")
                return self._check_normal_strategy(stock, today_data, hist_np)
            else:
                # 高位策略（强势股策略）
                logger.debug(f"{stock} 价格位置{price_position*100:.1f}% > {threshold*100:.0f}%，使用高位强势策略")
                return self._check_strong_stock_strategy(stock, today_data, hist_np, price_position_result)
                
        except Exception as e:
            logger.error(f"{stock} 策略检查异常: {str(e)}")
            return False
            
    # ============ 双轨策略：辅助方法 ============
    
    def _calculate_price_position(self, stock, today_data, closes):
        """
        计算价格位置（60日区间）
        """
        try:
            analysis_days = min(60, len(closes))  # 最多60天，不足则用实际天数
            
            if analysis_days < 20:
                return {'valid': False, 'reason': '数据不足（至少需要20个交易日）'}
            
            recent_closes = closes[-analysis_days:]
            recent_low = float(np.min(recent_closes))
            recent_high = float(np.max(recent_closes))
            price_range = recent_high - recent_low
            
            if price_range <= 0:
                return {'valid': False, 'reason': '价格区间无效'}
            
            current_price = today_data['last']
            price_position = (current_price - recent_low) / price_range
            
            return {
                'valid': True,
                'position': price_position,
                'low': recent_low,
                'high': recent_high,
                'analysis_days': analysis_days,
                'current_price': current_price
            }
            
        except Exception as e:
            logger.error(f"{stock} 计算价格位置失败: {str(e)}")
            return {'valid': False, 'reason': f'计算异常: {str(e)}'}
    
    # ============ 双轨策略：低位标准策略 ============
    
    def _check_normal_strategy(self, stock, today_data, hist_np):
        """
        低位标准策略（价格位置 <= 80%）
        条件：【硬指标】趋势向上 + 突破5日线 + 多头排列 + 量价关系 + 量价背离
        """
        try:
            # 获取当前回测时间
            current_time = today_data.get('time', datetime.now())
            
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')
            times = hist_np.get('time')

            # 跳过数据不足的情况
            if closes is None or volumes is None:
                logger.warning(f"{stock} 历史数据不足，无法计算条件")
                return False
            
            current_price = today_data['last']
            
            # ========== 统一指标预计算（所有指标一次性计算并存储到today_data）==========
            if not self.precalculate_indicators(stock, today_data, hist_np):
                logger.debug(f"❌ {stock} 指标预计算失败，数据不足")
                return False
            
            # ========== 硬指标：趋势判断（第一优先级）- 从today_data读取预计算结果 ==========
            if self.trend_filter_config.get('enabled', True):
                # 从预计算结果中读取趋势判断
                price_is_uptrend = today_data.get('price_is_uptrend', False)
                ma5_is_uptrend = today_data.get('ma5_is_uptrend', False)
                ma_is_divergent = today_data.get('ma_is_divergent', False)
                
                buy_config = self.trend_filter_config.get('buy', {})
                
                # 检查价格趋势
                if buy_config.get('price_trend', {}).get('enabled', True):
                    if not price_is_uptrend:
                        price_trend = today_data.get('price_trend', {})
                        logger.debug(f"❌ {stock} 价格趋势向下: {price_trend.get('trend_description', '未知')}")
                        return False
                    
                    # 检查趋势强度
                    min_strength = buy_config.get('price_trend', {}).get('min_strength', 0.4)
                    trend_strength = today_data.get('price_trend_strength', 0)
                    if trend_strength < min_strength:
                        logger.debug(f"❌ {stock} 价格趋势强度不足({trend_strength:.2f} < {min_strength})")
                        return False
                
                # 检查MA5趋势
                if buy_config.get('ma5_trend', {}).get('enabled', True):
                    if not ma5_is_uptrend:
                        ma5_trend = today_data.get('ma5_trend', {})
                        logger.debug(f"❌ {stock} MA5趋势向下: {ma5_trend.get('description', '未知')}")
                        return False
                    
                    # 检查斜率（下限 + 上限）
                    min_slope_pct = buy_config.get('ma5_trend', {}).get('min_slope_pct', 0.0)
                    max_slope_pct = buy_config.get('ma5_trend', {}).get('max_slope_pct', float('inf'))
                    ma5_slope_pct = today_data.get('ma5_slope_pct', 0)
                    if ma5_slope_pct < min_slope_pct:
                        logger.debug(f"❌ {stock} MA5斜率不足({ma5_slope_pct:.3f}% < {min_slope_pct}%)")
                        return False
                    if ma5_slope_pct > max_slope_pct:
                        logger.debug(f"❌ {stock} MA5斜率过高({ma5_slope_pct:.3f}% > {max_slope_pct}%)，可能涨多了")
                        return False
                
                # 检查MA10趋势（可选）
                if buy_config.get('ma10_trend', {}).get('enabled', False):
                    ma10_is_uptrend = today_data.get('ma10_is_uptrend', False)
                    if not ma10_is_uptrend:
                        ma10_trend = today_data.get('ma10_trend', {})
                        logger.debug(f"❌ {stock} MA10趋势向下: {ma10_trend.get('description', '未知')}")
                        return False
                    
                    # 检查MA10斜率下限
                    min_slope_pct = buy_config.get('ma10_trend', {}).get('min_slope_pct', 0.0)
                    ma10_slope_pct = today_data.get('ma10_slope_pct', 0)
                    if ma10_slope_pct < min_slope_pct:
                        logger.debug(f"❌ {stock} MA10斜率不足({ma10_slope_pct:.3f}% < {min_slope_pct}%)")
                        return False
                
                # 检查均线发散度
                if buy_config.get('ma_divergence', {}).get('enabled', True):
                    if not ma_is_divergent:
                        ma_divergence = today_data.get('ma_divergence', {})
                        logger.debug(f"❌ {stock} 均线发散度不足: {ma_divergence.get('description', '未知')}")
                        return False
                
                # 所有趋势检查通过
                price_trend = today_data.get('price_trend', {})
                ma5_trend = today_data.get('ma5_trend', {})
                logger.debug(f"✅ {stock} 趋势过滤通过: 价格{price_trend.get('trend_description', '未知')}, "
                           f"MA5{ma5_trend.get('description', '未知')}")
            
            # 买入敏感度过滤（复用卖出信号验证系统的时间分级）
            if self.sensitivity_analyzer and self.buy_sensitivity_config:
                try:
                    # 获取流动性等级（简化：默认使用medium，避免复杂计算）
                    liquidity = 'medium'  # 可选优化：调用 self.liquidity_analyzer.classify()
                    
                    # 分析时间敏感度
                    sensitivity = self.sensitivity_analyzer.analyze(current_time, liquidity)
                    
                    # 检查是否允许买入
                    buy_rule = self.buy_sensitivity_config.get(sensitivity, {})
                    if not buy_rule.get('allowed', True):
                        description = buy_rule.get('description', f'{sensitivity}敏感期')
                        logger.debug(f"🚫 {stock} {description}，暂不买入")
                        return False
                    
                    # 如果通过敏感度检查，记录日志
                    if sensitivity != 'normal':
                        logger.debug(f"✅ {stock} {sensitivity}敏感期检查通过，允许买入")
                        
                except Exception as e:
                    logger.warning(f"{stock} 敏感度分析异常（将继续买入逻辑）: {e}")
            
            # 检查冷静期（传递回测时间）
            if not self.trade_protector.check_repeat_buy_cooldown(stock, current_time):
                return False
            
            # 检查是否有足够的数据进行计算
            required_days = 30  # 留10天余量
            if len(closes) < required_days:
                logger.debug(f"❌ {stock} 历史数据不足：实际{len(closes)}天 < 需要{required_days}天")
                return False
            
            # 条件1: 突破5日均线
            if len(closes) < 5:
                return False
            
            ma5_previous = float(np.mean(closes[-5:]))
            if current_price <= ma5_previous:
                logger.debug(f"{stock} 未突破5日线")
                return False
                
            # 条件2: 多头排列 MA5>MA10>MA20（使用预计算的值）
            ma5 = today_data.get('ma5')
            ma10 = today_data.get('ma10')
            ma20 = today_data.get('ma20')
            
            if ma5 is None or ma10 is None or ma20 is None:
                logger.debug(f"{stock} 均线数据未预计算")
                return False
            
            if not (ma5 > ma10 > ma20):
                logger.debug(f"{stock} 非多头排列")
                return False
                
            # 条件3: 量价关系分析
            volume_price_result = self.analyze_volume_price_relationship(stock, today_data, hist_np)
            if not volume_price_result['valid']:
                logger.debug(f"{stock} 量价关系分析失败")
                return False
            
            # 条件4: 量价背离检测
            divergence_result = self.detect_volume_price_divergence(stock, today_data, hist_np)
            if not divergence_result['valid']:
                logger.debug(f"{stock} 量价背离检测失败")
                return False

            # 所有条件满足
            self.last_strategy_type = "低位标准策略"
            
            try:
                    info = xtdata.get_instrument_detail(stock)
                    stock_name = info.get('InstrumentName', '') if isinstance(info, dict) else ""
            except Exception:
                stock_name = ""
            
            # 模拟买入逻辑
            if not hasattr(self, 'mock_positions'):
                self.mock_positions = {}
            
            if stock not in self.mock_positions:
                current_price = today_data['last']
                self.mock_positions[stock] = current_price
                self.buy_stocks.add(stock)
                # 记录买入策略类型（用于卖出时显示）
                self.stock_strategy_types[stock] = self.last_strategy_type
                
                # 使用仓位管理模块计算仓位
                stock_type = volume_price_result.get('stock_type', 'mid')
                volume_strength = self._extract_volume_strength(volume_price_result)
                
                # 计算目标仓位
                target_position = self.position_calculator.calculate_target_position('low', stock_type, volume_strength)
                first_batch_ratio = self.position_calculator.calculate_first_batch_position(target_position)
                
                # 使用仓位管理器添加持仓（注意：实际shares由回测系统/交易执行器计算）
                from position_management import PositionInfo
                position_info_obj = PositionInfo(
                    stock=stock,
                    buy_time=current_time,
                    buy_price=current_price,
                    shares=0,  # 占位符，实际值由回测系统/交易执行器填充
                    strategy_type='low',
                    batch=1
                )
                self.position_manager.add_position(stock, position_info_obj)
                self.trade_protector.record_buy_time(stock, current_time)
                
                # 使用统一的日志输出方法（包含仓位信息）
                # 买入信号触发（低位标准策略）- 回测系统会统一处理日志
            
            return True
            
        except Exception as e:
            logger.error(f"{stock} 低位策略检查异常: {str(e)}")
            return False

    # ============ 双轨策略：高位强势策略 ============
    
    def _check_strong_stock_strategy(self, stock, today_data, hist_np, price_position_result):
        """
        高位强势策略（价格位置 > 80%）- 差异化标准
        条件：【硬指标】趋势向上 + 多头排列 + 均线间距（差异化）+ 量比（差异化）+ 量能位置（差异化）
        """
        try:
            # 获取当前回测时间
            current_time = today_data.get('time', datetime.now())
            
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')

            # 跳过数据不足的情况
            if closes is None or volumes is None:
                logger.warning(f"{stock} 历史数据不足，无法计算条件")
                return False
            
            current_price = today_data['last']
            
            # ========== 统一指标预计算（所有指标一次性计算并存储到today_data）==========
            if not self.precalculate_indicators(stock, today_data, hist_np):
                logger.debug(f"❌ {stock} 指标预计算失败，数据不足")
                return False
            
            # ========== 硬指标：趋势判断（第一优先级）- 从today_data读取预计算结果 ==========
            if self.trend_filter_config.get('enabled', True):
                # 从预计算结果中读取趋势判断
                price_is_uptrend = today_data.get('price_is_uptrend', False)
                ma5_is_uptrend = today_data.get('ma5_is_uptrend', False)
                ma_is_divergent = today_data.get('ma_is_divergent', False)
                
                buy_config = self.trend_filter_config.get('buy', {})
                
                # 趋势检查（与低位策略相同逻辑）
                if buy_config.get('price_trend', {}).get('enabled', True):
                    if not price_is_uptrend:
                        price_trend = today_data.get('price_trend', {})
                        logger.debug(f"❌ {stock} 价格趋势向下: {price_trend.get('trend_description', '未知')}")
                        return False
                    
                    min_strength = buy_config.get('price_trend', {}).get('min_strength', 0.4)
                    trend_strength = today_data.get('price_trend_strength', 0)
                    if trend_strength < min_strength:
                        logger.debug(f"❌ {stock} 价格趋势强度不足({trend_strength:.2f} < {min_strength})")
                        return False
                
                if buy_config.get('ma5_trend', {}).get('enabled', True):
                    if not ma5_is_uptrend:
                        ma5_trend = today_data.get('ma5_trend', {})
                        logger.debug(f"❌ {stock} MA5趋势向下: {ma5_trend.get('description', '未知')}")
                        return False
                    
                    min_slope_pct = buy_config.get('ma5_trend', {}).get('min_slope_pct', 0.0)
                    ma5_slope_pct = today_data.get('ma5_slope_pct', 0)
                    if ma5_slope_pct < min_slope_pct:
                        logger.debug(f"❌ {stock} MA5斜率不足({ma5_slope_pct:.3f}% < {min_slope_pct}%)")
                        return False
                
                if buy_config.get('ma10_trend', {}).get('enabled', False):
                    ma10_is_uptrend = today_data.get('ma10_is_uptrend', False)
                    if not ma10_is_uptrend:
                        ma10_trend = today_data.get('ma10_trend', {})
                        logger.debug(f"❌ {stock} MA10趋势向下: {ma10_trend.get('description', '未知')}")
                        return False
                
                if buy_config.get('ma_divergence', {}).get('enabled', True):
                    if not ma_is_divergent:
                        ma_divergence = today_data.get('ma_divergence', {})
                        logger.debug(f"❌ {stock} 均线发散度不足: {ma_divergence.get('description', '未知')}")
                        return False
                
                # 所有趋势检查通过
                price_trend = today_data.get('price_trend', {})
                ma5_trend = today_data.get('ma5_trend', {})
                logger.debug(f"✅ {stock} 趋势过滤通过(高位): 价格{price_trend.get('trend_description', '未知')}, "
                           f"MA5{ma5_trend.get('description', '未知')}")
            
            # 买入敏感度过滤（与低位策略相同）
            if self.sensitivity_analyzer and self.buy_sensitivity_config:
                try:
                    liquidity = 'medium'
                    sensitivity = self.sensitivity_analyzer.analyze(current_time, liquidity)
                    buy_rule = self.buy_sensitivity_config.get(sensitivity, {})
                    if not buy_rule.get('allowed', True):
                        description = buy_rule.get('description', f'{sensitivity}敏感期')
                        logger.debug(f"🚫 {stock} {description}，暂不买入")
                        return False
                    if sensitivity != 'normal':
                        logger.debug(f"✅ {stock} {sensitivity}敏感期检查通过，允许买入")
                except Exception as e:
                    logger.warning(f"{stock} 敏感度分析异常（将继续买入逻辑）: {e}")
            
            # 检查冷静期（传递回测时间）
            if not self.trade_protector.check_repeat_buy_cooldown(stock, current_time):
                return False
            
            if len(closes) < 60:
                logger.debug(f"{stock} 历史数据不足（需要60天）")
                return False
            
            # 从预计算结果中读取股票类型（已在precalculate_indicators中计算）
            stock_type = today_data.get('stock_type', 'mid')
            market_cap = today_data.get('market_cap', 0)
            
            # 检查股票类型（大中小盘）
            if stock_type == 'unknown':
                logger.debug(f"{stock} 无法获取流通市值，跳过高位强势策略")
                return False
            
            # 获取对应的差异化标准
            high_config = self.buy_params['strong_stock_strategy']
            ma_spacing_min = high_config['ma_spacing'][stock_type]
            volume_ratio_min = high_config['volume_ratio'][stock_type]
            volume_position_min = high_config['volume_position'][stock_type]
            
            # 获取股票类型名称（用于日志）
            stock_type_name = self.buy_params['normal_strategy']['volume_classification'][stock_type]['name']
            
            # 条件1: 多头排列（必要条件，使用预计算的值）
            ma5 = today_data.get('ma5')
            ma10 = today_data.get('ma10')
            ma20 = today_data.get('ma20')
            
            if ma5 is None or ma10 is None or ma20 is None:
                logger.debug(f"{stock} 高位策略均线数据未预计算")
                return False
            
            if not (ma5 > ma10 > ma20):
                logger.debug(f"{stock} 均线未多头排列")
                return False
            
            # 条件2: 均线间距检查（差异化标准）
            ma_spacing = (ma5 / ma20 - 1) * 100
            if ma_spacing < ma_spacing_min:
                logger.debug(f"{stock}[{stock_type_name}] 均线间距{ma_spacing:.2f}% < {ma_spacing_min}%")
                return False
            
            # 条件3: 量比检查（差异化标准）
            volume_ratio = self.get_volume_ratio(stock)
            if volume_ratio is None:
                logger.debug(f"{stock}[{stock_type_name}] 无法获取量比")
                return False
            if volume_ratio < volume_ratio_min:
                logger.debug(f"{stock}[{stock_type_name}] 量比{volume_ratio:.2f} < {volume_ratio_min}")
                return False
            
            # 条件4: 量能位置检查（差异化标准）
            volume_60d_min = float(np.min(volumes[-60:]))
            volume_60d_max = float(np.max(volumes[-60:]))
            volume_position = 0
            if volume_60d_max - volume_60d_min > 0:
                volume_position = (today_data['volume'] - volume_60d_min) / (volume_60d_max - volume_60d_min)
                if volume_position < volume_position_min:
                    logger.debug(f"{stock}[{stock_type_name}] 量能位置{volume_position:.2%} < {volume_position_min:.0%}")
                    return False
            else:
                logger.debug(f"{stock} 量能区间无效")
                return False
            
            # 所有条件满足
            self.last_strategy_type = "高位强势策略"
            
            try:
                info = xtdata.get_instrument_detail(stock)
                stock_name = info.get('InstrumentName', '') if isinstance(info, dict) else ""
            except Exception:
                stock_name = ""
            
            # 模拟买入逻辑
            if not hasattr(self, 'mock_positions'):
                self.mock_positions = {}
            
            if stock not in self.mock_positions:
                current_price = today_data['last']
                self.mock_positions[stock] = current_price
                self.buy_stocks.add(stock)
                # 记录买入策略类型（用于卖出时显示）
                self.stock_strategy_types[stock] = self.last_strategy_type
                
                # 使用仓位管理模块计算仓位
                # 高位策略量能强度根据量比判断
                if volume_ratio >= high_config['volume_ratio'][stock_type] * 1.5:
                    volume_strength = 'strong'
                elif volume_ratio >= high_config['volume_ratio'][stock_type]:
                    volume_strength = 'moderate'
                else:
                    volume_strength = 'normal'
                
                # 计算目标仓位
                target_position = self.position_calculator.calculate_target_position('high', stock_type, volume_strength)
                first_batch_ratio = self.position_calculator.calculate_first_batch_position(target_position)
                
                # 使用仓位管理器添加持仓（注意：实际shares由回测系统/交易执行器计算）
                from position_management import PositionInfo
                position_info_obj = PositionInfo(
                    stock=stock,
                    buy_time=current_time,
                    buy_price=current_price,
                    shares=0,  # 占位符，实际值由回测系统/交易执行器填充
                    strategy_type='high',
                    batch=1
                )
                self.position_manager.add_position(stock, position_info_obj)
                self.trade_protector.record_buy_time(stock, current_time)
                
                # 使用统一的日志输出方法（包含仓位信息）
                # 买入信号触发（高位强势策略）- 回测系统会统一处理日志
            
            return True
            
        except Exception as e:
            logger.error(f"{stock} 高位策略检查异常: {str(e)}")
            return False

    # ============ 工具方法 ============
    
    @staticmethod
    def format_volume(volume):
        """
        格式化成交量显示
        :param volume: 原始成交量
        :return: 格式化后的字符串（如：54.10万、1.23亿）
        """
        if volume >= 100000000:  # 1亿以上
            return f"{volume / 100000000:.2f}亿"
        elif volume >= 10000:  # 1万以上
            return f"{volume / 10000:.2f}万"
        else:
            return f"{volume:.0f}"

    def _calculate_volume_ratio_from_history(self, stock, today_volume=None):
        """
        从历史数据计算量比（私有方法，供内部调用）
        
        量比公式：当日成交量 / 过去5日平均成交量
        
        使用场景：
        - 回测模式：作为主要计算方法（当today_data['volRatio']未设置时）
        - 实盘模式：作为兜底计算方法（当缓存和API都失败时）
        
        Args:
            stock: 股票代码
            today_volume: 当天的成交量（如果提供，优先使用；否则尝试从today_data获取）
        
        Returns:
            float: 量比值（>0表示成功，0表示失败）
        """
        try:
            hist_np = self.hist_np_cache.get(stock)
            if hist_np is None:
                logger.debug(f"{stock} 缺少历史数据缓存，无法计算量比")
                return 0.0
            
            hist_volumes = hist_np.get('volume')
            if hist_volumes is None or len(hist_volumes) < 5:
                logger.debug(f"{stock} 历史成交量数据不足（需要至少5天），无法计算量比")
                return 0.0
            
            # 优先使用传入的当天成交量，避免盘中用前一天的数据
            if today_volume is not None and today_volume > 0:
                current_volume = float(today_volume)
                # 过去5日不包括当天，直接用最近5天
                past_5_days_volumes = hist_volumes[-5:]
            else:
                # 如果没有提供当天成交量，尝试从today_data获取
                today_data = self.today_data.get(stock, {})
                if 'volume' in today_data and today_data['volume'] > 0:
                    # today_data中有当天成交量（由回测系统/quote_callback/分析工具设置）
                    current_volume = float(today_data['volume'])
                    # 过去5日不包括当天，直接用最近5天
                    past_5_days_volumes = hist_volumes[-5:]
                else:
                    # 兜底：从历史缓存获取（此时缓存中的最后一天应该是当天）
                    # 注意：这种情况通常不应该发生，可能导致数据不准确
                    current_volume = float(hist_volumes[-1])
                    # 获取过去5日平均成交量（不包括当天）
                    past_5_days_volumes = hist_volumes[-6:-1]  # 取最近6天，不包括最后一天（当天）
                    if len(past_5_days_volumes) < 5:
                        # 如果不够5天，取全部历史数据（不包括当天）
                        past_5_days_volumes = hist_volumes[:-1]
            
            if len(past_5_days_volumes) == 0:
                logger.debug(f"{stock} 没有足够的历史数据计算量比")
                return 0.0
            
            past_avg_volume = float(np.mean(past_5_days_volumes))
            
            if past_avg_volume > 0:
                volume_ratio = current_volume / past_avg_volume
                volume_ratio = round(volume_ratio, 2)
                logger.debug(f"{stock} 回测模式量比: {volume_ratio:.2f} (当日:{current_volume:.0f}, 5日均:{past_avg_volume:.0f})")
                return float(volume_ratio)
            else:
                logger.debug(f"{stock} 过去5日平均成交量为0，无法计算量比")
                return 0.0
                
        except Exception as e:
            logger.warning(f"{stock} 从历史数据计算量比失败: {str(e)}")
            return 0.0

    def get_volume_ratio(self, stock):
        """
        获取股票的量比
        
        量比公式：现成交总手数 ÷ (过去5日平均每分钟成交量 × 当日累计开市时间(分钟))
        
        设计原则（统一缓存机制）：
        1. 优先读取 today_data['volRatio'] 缓存（避免重复计算/网络请求）
        2. 缓存未命中时，根据运行模式调用相应的计算方法
        3. 缓存由数据更新层负责写入：
           - 实盘/模拟/监控：quote_callback() 更新
           - 分时回测：MinuteStrategyWrapper 更新
           - 策略分析工具：_analyze_intraday_buy_conditions() 更新
        
        Returns:
            float: 量比值（>0表示成功，0表示失败）
        """
        try:
            # 统一优先使用 today_data 缓存（所有模式通用）
            today_data = self.today_data.get(stock, {})
            if 'volRatio' in today_data and today_data['volRatio'] is not None and today_data['volRatio'] > 0:
                # 缓存命中：直接返回（由数据更新层设置，参见函数文档）
                return float(today_data['volRatio'])
            
            # 缓存未命中：根据模式计算
            if getattr(self, 'backtest_mode', False):
                # 回测模式：使用历史数据计算
                return self._calculate_volume_ratio_from_history(stock)
            
            # 实盘/模拟盘模式：使用新的量比计算器（固定窗口实时量比/累计量比）
            if self.volume_ratio_calculator:
                try:
                    # 获取实时行情数据
                    quote_data = xtdata.get_full_tick([stock])
                    if quote_data and stock in quote_data:
                        quote = quote_data[stock]
                        time_tag = quote.get('timetag', '')
                        current_volume = quote.get('volume', 0)
                        
                        # 检查数据单位
                        if current_volume > 0 and current_volume < 1000:
                            current_volume = current_volume * 10000 / 100
                        
                        # 获取历史数据（转换为DataFrame格式）
                        hist_np = self.hist_np_cache.get(stock)
                        if hist_np is not None and 'volume' in hist_np:
                            import pandas as pd
                            historical_daily_data = pd.DataFrame({
                                'volume': hist_np['volume']
                            })
                            
                            # 使用固定窗口实时量比（累计量比）
                            trading_minutes = self.volume_ratio_calculator.calculate_trading_minutes(time_tag) if time_tag else None
                            volume_ratio = self.volume_ratio_calculator.calculate_cumulative_volume_ratio(
                                stock,
                                current_volume=float(current_volume) if current_volume else 0,
                                historical_daily_data=historical_daily_data,
                                actual_trading_minutes=trading_minutes,
                                time_tag=time_tag
                            )
                            
                            if volume_ratio > 0:
                                return float(volume_ratio)
                except Exception as e:
                    logger.debug(f"{stock} 使用新量比计算器失败: {e}，回退到旧方法")
            
            # 回退到旧方法（兜底）
            quote_data = xtdata.get_full_tick([stock])
            logger.debug(f"{stock} 实时行情数据: {quote_data}")
            if quote_data and stock in quote_data:
                quote = quote_data[stock]
                
                # 1. 尝试直接获取量比字段（多种可能的字段名）
                ratio_fields = ['volRatio', 'volumeRatio', 'volume_ratio', 'turnoverRatio']
                for field in ratio_fields:
                    if field in quote and quote[field] is not None:
                        ratio_value = quote[field]
                        if isinstance(ratio_value, (int, float)) and ratio_value > 0:
                            logger.debug(f"{stock} 从{field}字段获取量比: {ratio_value}")
                            return float(ratio_value)
                
                # 2. 手动计算正确的量比（旧方法）
                current_volume = quote.get('volume', 0)
                time_tag = quote.get('timetag', '')
                
                if current_volume > 0 and current_volume < 1000:
                    current_volume = current_volume * 10000 / 100
                
                hist_np = self.hist_np_cache.get(stock)
                if hist_np is not None:
                    hist_volumes = hist_np.get('volume')
                    if hist_volumes is not None and len(hist_volumes) >= 5:
                        past_5_days_volumes = hist_volumes[-5:]
                        if np.mean(past_5_days_volumes) > 0 and np.mean(past_5_days_volumes) < 1000:
                            past_5_days_volumes = past_5_days_volumes * 10000 / 100
                        
                        past_5_days_avg_volume = float(np.mean(past_5_days_volumes))
                        # 使用量比计算器的方法
                        trading_minutes = self.volume_ratio_calculator.calculate_trading_minutes(time_tag) if self.volume_ratio_calculator else 0
                        
                        if trading_minutes > 0 and past_5_days_avg_volume > 0:
                            daily_trading_minutes = 240
                            past_5_days_avg_per_minute = past_5_days_avg_volume / daily_trading_minutes
                            expected_volume = past_5_days_avg_per_minute * trading_minutes
                            volume_ratio = current_volume / expected_volume if expected_volume > 0 else 0
                            return round(volume_ratio, 2)
            
            return 0.0
        except Exception as e:
            logger.warning(f"{stock} 获取量比失败: {str(e)}")
            return 0.0
    
    
    def _get_time_factor_simple(self, hour, minute=0):
        """
        获取时间因子（简化版）
        基于config.yaml配置
        
        Args:
            hour: 当前小时 (9-14)
            minute: 当前分钟 (0-59)，可选
        
        Returns:
            float: 时间因子 (0.3-1.0)
        """
        # 从配置读取时间因子
        time_config = self.strategy_config.get('volume_validation', {}).get('intraday_actual', {}).get('time_adjustment', {})
        
        if not time_config:
            # 配置不存在时的默认值
            time_config = {
                'morning_early': 0.3,
                'morning_mid': 0.6,
                'morning_late': 0.8,
                'afternoon_early': 0.9,
                'afternoon_late': 1.0
            }
        
        # 根据小时判断时间段
        if hour == 9:  # 9:30-10:00
            return time_config.get('morning_early', 0.3)
        elif hour == 10:  # 10:00-11:00
            return time_config.get('morning_mid', 0.6)
        elif hour == 11:  # 11:00-11:30
            return time_config.get('morning_late', 0.8)
        elif hour == 13:  # 13:00-14:00
            return time_config.get('afternoon_early', 0.9)
        elif hour == 14:  # 14:00-15:00
            return time_config.get('afternoon_late', 1.0)
        else:
            return 0.5  # 其他时间段默认值
    
    def check_volume_validation_time_aware(self, stock, today_data, hist_np, volume_status, stock_type):
        """
        时间感知的20日均量验证
        基于已完成成交量，不预估未来
        
        设计原则：
        - 只使用截至当前时刻的实际成交量
        - 通过时间因子调整阈值（早盘宽松，尾盘严格）
        - 确保回测与实盘逻辑完全一致
        
        Args:
            stock: 股票代码
            today_data: 当日数据
            hist_np: 历史数据
            volume_status: 量能状态（"放量"/"温和放量"等）
            stock_type: 股票类型（'large'/'mid'/'small'）
        
        Returns:
            tuple: (是否通过, 验证原因说明)
        """
        try:
            # 检查是否启用
            vol_config = self.strategy_config.get('volume_validation', {}).get('intraday_actual', {})
            if not vol_config.get('enabled', True):
                return True, "验证功能未启用"
            
            volumes = hist_np.get('volume')
            if volumes is None or len(volumes) < 20:
                return True, "数据不足(<20天)"
            
            vol_ma20 = float(np.mean(volumes[-20:]))
            current_volume = today_data.get('volume', 0)  # 截至当前的实际成交量
            
            if current_volume <= 0:
                return False, "当前成交量为0"
            
            # 获取当前时间
            time_tag = today_data.get('timetag', '')
            if not time_tag:
                return True, "无时间信息"
            
            try:
                time_str = time_tag.split()[1]  # "10:30:00"
                hour, minute = map(int, time_str.split(':')[:2])
            except:
                return True, "时间解析失败"
            
            # 获取时间因子（简化版）
            time_factor = self._get_time_factor_simple(hour, minute)
            
            # 获取基础阈值（从配置读取）
            base_thresholds = vol_config.get('base_thresholds', {})
            stock_type_key = {
                'large': 'large_cap',
                'mid': 'mid_cap',
                'small': 'small_cap'
            }.get(stock_type, 'mid_cap')
            
            base_threshold = base_thresholds.get(stock_type_key, 1.2)
            
            # 计算调整后的阈值
            adjusted_threshold = base_threshold * time_factor
            
            # 验证（只对放量和温和放量进行验证）
            if volume_status in ["放量", "温和放量"]:
                vol_ma20_pass = current_volume > vol_ma20 * adjusted_threshold
                
                reason = (f"{hour:02d}:{minute:02d} | "
                         f"实际量{self.format_volume(current_volume)} vs "
                         f"要求{self.format_volume(vol_ma20 * adjusted_threshold)} | "
                         f"因子{time_factor:.1f} | "
                         f"{'✅通过' if vol_ma20_pass else '❌未过'}")
                
                return vol_ma20_pass, reason
            else:
                # 其他量能状态不验证
                return True, f"{volume_status}状态无需验证"
            
        except Exception as e:
            logger.warning(f"{stock} 时间感知量能验证失败: {str(e)}")
            return True, "验证异常，放行"

    def _evaluate_technical_signal_unified(self, stock: str, strategy_signal: Optional[Dict] = None) -> Dict:
        """
        使用统一接口评估技术形态信号 简化接口
        
        整合独立触发（条件D）和融合增强（条件C+技术）的统一入口
        
        Args:
            stock: 股票代码
            strategy_signal: 策略信号（可选），如果为None则只检查独立触发
        
        Returns:
            评估结果字典：
            {
                'action': 'independent' | 'enhanced' | 'none',
                'signal': {...},  # 最终信号
                'tech_signals': [...]  # 原始技术信号
            }
        """
        # 检查技术引擎是否初始化
        if not self.technical_engine:
            return {'action': 'none', 'signal': strategy_signal, 'tech_signals': []}
        
        # 获取当前数据
        today_data = self.today_data.get(stock)
        if not today_data or not isinstance(today_data, dict):
            return {'action': 'none', 'signal': strategy_signal, 'tech_signals': []}
        
        # 准备持仓信息
        position_info = None
        position = self.position_manager.get_position(stock)
        current_price = today_data.get('last', 0)
        
        if position:
            profit_ratio = (current_price - position.buy_price) / position.buy_price
            position_info = {
                'buy_price': position.buy_price,
                'current_price': current_price,
                'profit_ratio': profit_ratio
            }
        
        # 准备预计算指标
        metrics = {
            'volume_ratio': today_data.get('volRatio', 0),
            'price_position': today_data.get('price_position', 0),
            'avg_volume': today_data.get('avg_volume', 0)
        }
        
        # 调用统一评估接口 简化集成
        try:
            tech_result = self.technical_engine.evaluate_sell_signal(
                stock=stock,
                strategy_signal=strategy_signal,
                today_data=today_data,
                position_info=position_info,
                mode='intraday',
                metrics=metrics
            )
            return tech_result
        except Exception as e:
            logger.debug(f"[{stock}] 技术形态评估异常: {e}")
            return {'action': 'none', 'signal': strategy_signal, 'tech_signals': []}
    
    def _enhance_signal_with_technical_patterns(self, stock: str, sell_signal: Dict, 
                                                  current_price: float, today_data: Dict) -> Dict:
        """
        使用技术形态增强策略卖出信号（融合模式）已简化为调用统一接口
        
        不独立决策，而是增强策略信号的强度和置信度
        
        Args:
            stock: 股票代码
            sell_signal: 策略卖出信号
            current_price: 当前价格
            today_data: 当日累计数据
        
        Returns:
            增强后的卖出信号（可能修改strength、confidence等字段）
        """
        # 检查技术引擎是否初始化
        if not self.technical_engine:
            logger.debug(f"[{stock}] 技术引擎未初始化，跳过技术形态增强")
            return sell_signal
            
        if not sell_signal.get('should_sell', False):
            return sell_signal
        
        # 使用统一接口评估技术形态（简化集成）
        tech_result = self._evaluate_technical_signal_unified(stock, strategy_signal=sell_signal)
        
        # 修复：优先处理独立触发（条件D）
        if tech_result['action'] == 'independent':
            # 技术形态达到独立触发阈值，应该切换到条件D而不是增强条件A
            tech_signal = tech_result['signal']
            # 从配置读取 bypass_validation，默认值为 True（保持向后兼容）
            independent_mode = self.tech_enhancement_config.get('independent_mode', {}) if self.tech_enhancement_config else {}
            bypass_validation = independent_mode.get('bypass_validation', True)
            return {
                'should_sell': True,
                'reason': tech_signal.get('reason'),
                'sell_ratio': tech_signal.get('sell_ratio', 1.0),
                'source': 'technical_independent',
                'condition': 'D',
                'strength': tech_signal.get('tech_strength'),
                'tech_signal': tech_signal,
                'bypass_validation': bypass_validation,  # 从配置读取是否绕过验证
                'details': tech_signal.get('details', {})
            }
        
        # 根据评估结果处理
        if tech_result['action'] == 'enhanced':
            # 技术形态增强了策略信号
            enhanced_signal = tech_result['signal']
            # 保持原有字段兼容性
            sell_signal['enhanced_strength'] = sell_signal.get('strength', 1.0) * (1.0 + enhanced_signal.get('tech_enhancement', 0))
            sell_signal['tech_enhancement'] = enhanced_signal.get('tech_enhancement', 0)
            sell_signal['tech_confirmed'] = True
            sell_signal['tech_strength'] = enhanced_signal.get('tech_strength')
            sell_signal['pattern'] = enhanced_signal.get('pattern')
            # 更新原因（如果有技术形态信息）
            if 'reason' in enhanced_signal:
                sell_signal['reason'] = enhanced_signal['reason']
            # 更新卖出比例（如果被调整）
            if 'sell_ratio' in enhanced_signal:
                sell_signal['sell_ratio'] = enhanced_signal['sell_ratio']
            return sell_signal
        
        # 无增强（保持原样）
        return sell_signal
    
    
    def _log_tech_pattern_not_triggered(self, stock: str, today_data: Dict):
        """
        输出技术形态未触发的诊断信息（为什么不满足触发条件），使用去重机制避免重复输出
        
        改进：按"日期+股票+原因"去重，允许不同原因多次输出
        
        Args:
            stock: 股票代码
            today_data: 当日累计数据
        """
        # 去重：每天每个股票的每种原因只输出一次
        if not hasattr(self, '_logged_tech_diagnosis'):
            self._logged_tech_diagnosis = {}  # 改为dict，记录输出时间
        
        # 获取日期
        date_str = today_data.get('date', '') if isinstance(today_data, dict) else ''
        if not date_str:
            from datetime import datetime
            date_str = datetime.now().strftime('%Y%m%d')
        
        # 计算实际数据
        try:
            high = today_data.get('high', 0)
            low = today_data.get('low', 0)
            current_price = today_data.get('last', 0) or today_data.get('close', 0)
            
            # 计算振幅和上影线
            total_range = high - low
            upper_shadow = high - current_price
            upper_ratio = upper_shadow / total_range if total_range > 0 else 0
            
            # 获取配置阈值（大盘股）
            from config_loader import TECHNICAL_PATTERNS_CONFIG
            sell_config = TECHNICAL_PATTERNS_CONFIG.get('upper_shadow', {}).get('sell_signals', {}).get('large_cap', {})
            shadow_threshold = sell_config.get('shadow_ratio', 0.25)
            volume_min = sell_config.get('volume_min', 0.2)
            position_min = sell_config.get('position_min', 0.3)
            
            # 获取量比和价格位置（如果有预计算数据）
            volume_ratio = today_data.get('volume_ratio', 0)
            price_position = today_data.get('price_position', 0)
            
            # 按照技术形态检测逻辑的顺序，逐一检查不满足的条件
            diag_parts = []
            
            # 1. 最小振幅检查（参考technical_engine中min_kline_range配置）
            from config_loader import TECHNICAL_PATTERNS_CONFIG
            min_range = TECHNICAL_PATTERNS_CONFIG.get('upper_shadow', {}).get('min_kline_range', 0.5)
            if total_range < min_range:
                diag_parts.append(f"振幅不足(振幅{total_range:.2f}<{min_range:.2f})")
            # 如果振幅不足，无法形成有效的上影线，后续条件无需检查
            elif total_range <= 0.01:
                diag_parts.append(f"振幅过小(振幅{total_range:.2f}≤0.01，无法形成上影线)")
            else:
                # 振幅满足，继续检查其他条件（按照_check_sell_pattern的检查顺序）
                
                # 2. 上影线比例检查（第一个检查点）
                if upper_ratio < shadow_threshold:
                    diag_parts.append(f"上影线比例不足({upper_ratio:.2%}<{shadow_threshold:.2%})")
                
                # 3. 量比检查（第二个检查点，仅在上影线比例满足时检查）
                elif volume_ratio <= 0:
                    # 量比未计算或无效
                    pass  # 不输出，因为可能策略没有计算
                elif volume_ratio < volume_min:
                    diag_parts.append(f"量比不足({volume_ratio:.2f}<{volume_min:.2f})")
                
                # 4. 价格位置检查（第三个检查点，仅在前两个满足时检查）
                elif price_position <= 0:
                    # 价格位置未计算或无效
                    pass  # 不输出，因为可能策略没有计算
                elif price_position < position_min:
                    diag_parts.append(f"价格位置不足({price_position:.2%}<{position_min:.2%})")
                
                # 如果前面条件都满足，可能是信号强度不足（但这是另一个层次的判断，不在此处输出）
            
            # 确定诊断原因（用于去重key）
            if diag_parts:
                reason = ', '.join(diag_parts)
            else:
                reason = "基础条件满足，可能强度不足"
            
            # 生成去重key（日期+股票+原因）改进：按原因去重
            diagnosis_key = f"{date_str}_{stock}_{reason}"
            
            # 去重检查：每种原因只输出一次
            if diagnosis_key in self._logged_tech_diagnosis:
                return  # 该原因已输出过，跳过
            
            # 改为DEBUG级别，减少日志噪音（仅诊断用）
            data_info = f"上影线{upper_ratio:.2%} 最高{high:.2f} 当前{current_price:.2f} 振幅{total_range:.2f}"
            logger.debug(f"📊 [{stock}] 技术形态未触发: {reason} | 数据:{data_info}")
            
            # 标记已输出（记录输出时间，未来可以实现定期重新输出）
            import time
            self._logged_tech_diagnosis[diagnosis_key] = time.time()
            
        except Exception as e:
            logger.debug(f"[{stock}] 技术形态诊断信息获取失败: {e}")
    
    def _check_technical_independent(self, stock: str) -> Optional[Dict]:
        """
        检查技术形态是否满足独立触发条件（条件D）已简化为调用统一接口
        
        独立触发要求：
        1. 独立模式已启用（work_mode='hybrid'或'independent'）
        2. 强度≥独立触发阈值（如2.5）
        3. 使用更严格的阈值配置
        
        Returns:
            如果满足独立触发条件，返回卖出信号；否则返回None
        """
        # 使用统一接口评估（简化集成）
        tech_result = self._evaluate_technical_signal_unified(stock, strategy_signal=None)
        
        # 检查是否独立触发
        if tech_result['action'] == 'independent':
            # 转换为策略信号格式
            tech_signal = tech_result['signal']
            # 从配置读取 bypass_validation，默认值为 True（保持向后兼容）
            independent_mode = self.tech_enhancement_config.get('independent_mode', {}) if self.tech_enhancement_config else {}
            bypass_validation = independent_mode.get('bypass_validation', True)
            return {
                'should_sell': True,
                'reason': tech_signal.get('reason'),
                'sell_ratio': tech_signal.get('sell_ratio', 1.0),
                'source': 'technical_independent',
                'condition': 'D',
                'strength': tech_signal.get('tech_strength'),
                'tech_signal': tech_signal,
                'bypass_validation': bypass_validation,  # 从配置读取是否绕过验证
                'details': tech_signal.get('details', {})
            }
        
        return None
    
    def check_sell_conditions(self, stock: str) -> Dict:
        """
        检查是否满足卖出条件（差异化策略：大中小盘股）
        
        卖出条件（满足其一即卖出）：
        A. 高位缩量卖出 - 假突破预警
        B. 突破前高 + 异常放量 - 出货嫌疑判断
           B1. 天量出货（无条件卖出）
           B2. 放量滞涨（涨幅<3%时卖出）
        C. 跌破5日线 + 放量确认 - 短期趋势破位
        D. 技术形态独立触发 - 强技术信号（强度≥2.5）新增
        
        :param stock: 股票代码
        :return: 卖出信号字典 {'should_sell': bool, 'reason': str, 'details': dict}
        """
        # ===== 决策日志功能暂时禁用 =====
        # 原因：实现复杂度高，需要更多时间完善
        # TODO: 后续完善决策日志功能
        
        # ===== 优先级0：检查技术形态独立触发（条件D）新增 =====
        # 先获取通用数据，用于日志打印
        try:
            hist_np = self.hist_np_cache.get(stock)
            today_data = self.today_data.get(stock)
            
            if hist_np and today_data:
                closes = hist_np.get('close')
                volumes = hist_np.get('volume')
                if closes is not None and volumes is not None and len(closes) >= 30:
                    current_price = today_data['last']
                    volume_ratio = self.get_volume_ratio(stock)
                    vol_ma20 = float(np.mean(volumes[-20:])) if len(volumes) >= 20 else today_data.get('volume', 0)
                    
                    # 计算价格位置
                    price_position_result = self._calculate_price_position(stock, today_data, closes)
                    price_position = price_position_result.get('position', 0) if price_position_result.get('valid', False) else 0
                    today_data['price_position'] = price_position
                    
                    # 获取股票类型
                    stock_type = self.get_stock_type(stock)
                    if stock_type == 'unknown':
                        stock_type = 'mid'
                    stock_type_name = self.sell_params['high_shrink'][stock_type]['name']
        except:
            pass
        
        if self.technical_engine and self.tech_enhancement_config:
            tech_independent_signal = self._check_technical_independent(stock)
            if tech_independent_signal:
                # 条件D触发
                return tech_independent_signal  # 条件D触发，立即返回
        
        # ===== 优先级1：检查策略条件 A/B/C =====
        try:
            # 获取历史数据和当日数据（如果前面已经获取过，这里直接使用）
            if 'hist_np' not in locals():
                hist_np = self.hist_np_cache.get(stock)
            if 'today_data' not in locals() or today_data is None:
                today_data = self.today_data.get(stock)
            
            if hist_np is None or today_data is None:
                return {'should_sell': False, 'reason': '数据不足'}
            
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')
            
            # 卖出条件只需要基本均线数据，不需要60天的价格位置分析
            if closes is None or volumes is None or len(closes) < 30:
                return {'should_sell': False, 'reason': '历史数据不足'}
                
            current_price = today_data['last']
            current_volume = today_data['volume']
            
            # ========== 统一指标预计算（与买入逻辑使用相同方法）==========
            if not self.precalculate_indicators(stock, today_data, hist_np):
                logger.debug(f"{stock} 指标预计算失败（卖出检查），使用默认值")
            
            # ========== 从today_data读取预计算的指标 ==========
            stock_type = today_data.get('stock_type', 'mid')
            market_cap = today_data.get('market_cap', 0)
            stock_type_name = self.sell_params['high_shrink'][stock_type]['name']
            
            # 读取预计算的量比和价格位置
            volume_ratio = today_data.get('volume_ratio', self.get_volume_ratio(stock))
            vol_ma20 = today_data.get('vol_ma20', float(np.mean(volumes[-20:])) if len(volumes) >= 20 else current_volume)
            price_position = today_data.get('price_position', 0)
            upper_ratio = today_data.get('upper_ratio', 0)
            
            # ========== 条件A：高位缩量卖出（差异化标准）==========
            # 注意：技术形态已融合到策略条件中，在条件触发后通过_enhance_signal_with_technical_patterns增强
            if len(closes) >= 30:
                # 计算前期高位（前30个交易日）
                previous_high = float(np.max(closes[-30:-1]))
                high_threshold = previous_high * 0.98
                
                if current_price >= high_threshold:
                    # 获取该类型股票的缩量标准
                    params_a = self.sell_params['high_shrink'][stock_type]
                    
                    # 差异化判断：大盘0.8/中盘0.7/小盘0.6
                    # 卖出不使用时间因子：直接用实际成交量判断，要求更严格
                    if (volume_ratio < params_a['volume_ratio'] and 
                        current_volume < vol_ma20 * params_a['vol_ma20_ratio']):
                        
                        breakthrough_msg = "突破" if current_price > previous_high else "接近"
                        
                        # 获取买入时的策略类型
                        buy_strategy = self.stock_strategy_types.get(stock, "未知策略")
                        
                        sell_reason = (f"({buy_strategy}) 条件A-{stock_type_name}高位缩量卖出: {breakthrough_msg}前高"
                                     f"{previous_high:.2f} + 当前{current_price:.2f} + "
                                     f"量比{volume_ratio:.2f}<{params_a['volume_ratio']} + "
                                     f"成交量{self.format_volume(current_volume)}<20日均量×{params_a['vol_ma20_ratio']}")
                        
                        # ===== 新增：分批减仓逻辑 =====
                        # 使用仓位管理器的条件A计数
                        current_count = self.position_manager.increment_condition_a_count(stock)
                        
                        # 使用仓位计算器计算减仓比例
                        sell_ratio = self.position_calculator.calculate_sell_ratio('A', current_count)
                        
                        # 根据触发次数决定动作描述
                        if current_count == 1:
                            sell_action = f"减仓{sell_ratio*100:.0f}%"
                        elif current_count == 2:
                            sell_action = f"再减仓{sell_ratio*100:.0f}%"
                        else:
                            sell_action = "清仓剩余30%"
                        
                        # 获取持仓信息
                        position = self.position_manager.get_position(stock)
                        position_log_info = None
                        if position:
                            position_log_info = {
                                'buy_price': position.buy_price,
                                'current_price': current_price,
                                'shares': position.shares,
                                'sell_ratio': sell_ratio
                            }
                        
                        # 使用统一的日志输出方法
                        details = {
                            '当前价格': f"{current_price:.2f} | 前期高位: {previous_high:.2f}",
                            '量比': f"{volume_ratio:.2f} < {params_a['volume_ratio']} (缩量标准)",
                            '成交量': f"{self.format_volume(current_volume)} < {self.format_volume(vol_ma20*params_a['vol_ma20_ratio'])} (20日均量×{params_a['vol_ma20_ratio']})",
                            '分批减仓': f"第{current_count}次触发 → {sell_action}"
                        }
                        # 卖出信号触发（回测系统会统一处理）
                        
                        sell_signal = {
                            'should_sell': True,
                            'reason': sell_reason,
                            'sell_ratio': sell_ratio,  # 新增：卖出比例
                            'sell_action': sell_action,  # 新增：卖出动作描述
                            'details': {
                                'current_price': current_price,
                                'previous_high': previous_high,
                                'volume_ratio': volume_ratio,
                                'price_position': price_position,  # 添加价格位置，用于技术形态指标输出
                                'market_cap': market_cap,
                                'stock_type': stock_type_name,
                                'signal_type': '高位缩量',
                                'breakthrough': current_price > previous_high,
                                'condition_a_count': current_count
                            }
                        }
                        
                        # 条件A信号（触发且执行）
                        
                        # 使用技术形态增强信号（融合模式）
                        sell_signal = self._enhance_signal_with_technical_patterns(stock, sell_signal, current_price, today_data)
                        return sell_signal
            
            # ========== 条件B：突破前高 + 异常放量（差异化标准）==========
            if len(closes) >= 30:
                # 计算前期高位
                previous_high = float(np.max(closes[-30:-1]))
                
                # 判断是否真正突破前期高位（突破1%以上）
                if current_price > previous_high * 1.01:
                    # 计算当日涨幅
                    prev_close = closes[-1]
                    price_change_pct = (current_price - prev_close) / prev_close * 100
                    
                    # 获取动态量能阈值（如果启用）或固定阈值
                    if self.dynamic_threshold_calculator is not None:
                        try:
                            threshold_result = self.dynamic_threshold_calculator.get_extreme_volume_threshold(
                                stock=stock,
                                hist_np=hist_np,
                                stock_type=stock_type
                            )
                            volume_ratio_threshold = threshold_result['volume_ratio_threshold']
                            vol_ma20_ratio_threshold = threshold_result['vol_ma20_ratio_threshold']
                            method = threshold_result.get('method', 'fixed')
                            
                            if method == 'dynamic':
                                # 去重检查：使用与程序其他去重机制一致的方式（策略层面通用，所有模式都使用）
                                cache_key = threshold_result.get('cache_key', '')
                                log_key = (stock, cache_key)  # 使用(股票代码, cache_key)作为去重key
                                
                                if log_key not in self.logged_threshold_calculations:
                                    self.logged_threshold_calculations.add(log_key)
                                    dynamic_raw = threshold_result.get('dynamic_raw', 0)
                                    min_threshold = threshold_result.get('min_threshold', 0)
                                    logger.info(f"{stock} 动态阈值计算: 原始={dynamic_raw:.2f}, 下限={min_threshold:.2f}, 最终={volume_ratio_threshold:.2f}, 20日均量倍数={vol_ma20_ratio_threshold:.2f}")
                            else:
                                logger.debug(f"{stock} 使用固定阈值: 量比={volume_ratio_threshold:.2f}, 20日均量倍数={vol_ma20_ratio_threshold:.2f}")
                        except Exception as e:
                            logger.warning(f"{stock} 动态阈值计算失败，使用固定阈值: {e}")
                            # 回退到固定阈值
                            params_b1 = self.sell_params['extreme_volume'][stock_type]
                            volume_ratio_threshold = params_b1['volume_ratio']
                            vol_ma20_ratio_threshold = params_b1['vol_ma20_ratio']
                            method = 'fixed_fallback'
                    else:
                        # 使用固定阈值（未启用动态计算）
                        params_b1 = self.sell_params['extreme_volume'][stock_type]
                        volume_ratio_threshold = params_b1['volume_ratio']
                        vol_ma20_ratio_threshold = params_b1['vol_ma20_ratio']
                        method = 'fixed'
                    
                    # 获取滞涨标准（B2条件仍使用固定阈值）
                    params_b2 = self.sell_params['volume_lag'][stock_type]
                    
                    # 情况B1：天量出货（无条件卖出）
                    # 使用动态阈值或固定阈值（根据配置决定）
                    # 卖出不使用时间因子：盘中异常放量立即响应，保持严格
                    if (volume_ratio > volume_ratio_threshold or 
                        current_volume > vol_ma20 * vol_ma20_ratio_threshold):
                        
                        # 获取买入时的策略类型
                        buy_strategy = self.stock_strategy_types.get(stock, "未知策略")
                        
                        # 在卖出原因中显示使用的阈值和方法
                        threshold_method_text = "动态阈值" if method == 'dynamic' else "固定阈值"
                        trigger_condition = ""
                        if volume_ratio > volume_ratio_threshold:
                            trigger_condition = f"量比{volume_ratio:.2f}>{volume_ratio_threshold:.2f}({threshold_method_text})"
                        elif current_volume > vol_ma20 * vol_ma20_ratio_threshold:
                            trigger_condition = f"成交量>{vol_ma20*vol_ma20_ratio_threshold:.0f}(20日均量×{vol_ma20_ratio_threshold:.2f},{threshold_method_text})"
                        
                        sell_reason = (f"({buy_strategy}) 条件B1-{stock_type_name}天量出货: 突破前高"
                                     f"{previous_high:.2f} + 当前{current_price:.2f} + "
                                     f"{trigger_condition} + 涨幅{price_change_pct:+.2f}%")
                        
                        # 获取持仓信息
                        position = self.position_manager.get_position(stock)
                        position_log_info = None
                        if position:
                            position_log_info = {
                                'buy_price': position.buy_price,
                                'current_price': current_price,
                                'shares': position.shares,
                                'sell_ratio': 1.0  # 条件B1全部清仓
                            }
                        
                        # 使用统一的日志输出方法
                        threshold_method_text = "动态阈值" if method == 'dynamic' else "固定阈值"
                        details = {
                            '当前价格': f"{current_price:.2f} | 前期高位: {previous_high:.2f} | 突破幅度: {(current_price/previous_high-1)*100:.2f}%",
                            '当日涨幅': f"{price_change_pct:+.2f}% | 量比: {volume_ratio:.2f} ({threshold_method_text}>{volume_ratio_threshold:.2f})",
                            '成交量': f"{self.format_volume(current_volume)} | 20日均量×{vol_ma20_ratio_threshold:.2f}: {self.format_volume(vol_ma20*vol_ma20_ratio_threshold)}",
                            '判断': f"极度异常放量（{threshold_method_text}），可能是主力出货信号"
                        }
                        # ⚠️ 注释掉策略层日志输出，避免与回测系统的验证日志重复
                        # self.log_sell_signal(stock, buy_strategy, sell_reason, stock_type_name, market_cap, details, position_log_info)
                        
                        sell_signal = {
                            'should_sell': True,
                            'reason': sell_reason,
                            'sell_ratio': 1.0,  # 条件B1全部清仓
                            'details': {
                                'current_price': current_price,
                                'previous_high': previous_high,
                                'volume_ratio': volume_ratio,
                                'volume_ratio_threshold': volume_ratio_threshold,  # 使用的阈值
                                'vol_ma20_ratio_threshold': vol_ma20_ratio_threshold,  # 20日均量倍数阈值
                                'threshold_method': method,  # 阈值计算方法: dynamic/fixed/fixed_fallback
                                'price_position': price_position,  # 添加价格位置，用于技术形态指标输出
                                'price_change_pct': price_change_pct,
                                'market_cap': market_cap,
                                'stock_type': stock_type_name,
                                'signal_type': '高位天量出货',
                                'breakthrough': True
                            }
                        }
                        
                        # 条件B1信号（天量出货，全仓卖出）
                        
                        # 使用技术形态增强信号（融合模式）
                        sell_signal = self._enhance_signal_with_technical_patterns(stock, sell_signal, current_price, today_data)
                        return sell_signal
                    
                    # 情况B2：放量滞涨（涨幅<配置阈值时卖出）
                    # 大盘2.0/中盘2.8/小盘3.5 且涨幅<配置阈值
                    elif ((volume_ratio > params_b2['volume_ratio'] or 
                           current_volume > vol_ma20 * params_b2['vol_ma20_ratio']) and 
                          price_change_pct < params_b2['price_increase_limit']):
                        
                        # ===== 新增保护1：持仓时间保护 =====
                        position = self.position_manager.get_position(stock)
                        if position:
                            buy_time = position.buy_time
                            current_time = datetime.now()
                            
                            # 检查最小持仓时间（记录日志但不影响后续判断）
                            if not self.trade_protector.check_holding_time(stock, buy_time, current_time):
                                logger.debug(f"{stock} 未满足最小持仓时间，B2暂不触发")
                        
                        # ===== B2保护机制检查 =====
                        # 读取保护参数
                        min_breakthrough = self.sell_params['volume_lag'].get('min_breakthrough_pct', 2.0)
                        min_increase = self.sell_params['volume_lag'].get('min_increase_for_breakthrough', 2.0)
                        
                        should_skip_b2 = False
                        
                        # 保护1：持仓时间保护
                        position = self.position_manager.get_position(stock)
                        if position:
                            buy_time = position.buy_time
                            current_time = datetime.now()
                            if not self.trade_protector.check_holding_time(stock, buy_time, current_time):
                                should_skip_b2 = True
                                logger.debug(f"{stock} 持仓时间不足，B2不触发")
                        
                        # 保护2：前高距离保护
                        if not should_skip_b2:
                            breakthrough_pct = (current_price - previous_high) / previous_high * 100
                            if breakthrough_pct < min_breakthrough and price_change_pct >= min_increase:
                                should_skip_b2 = True
                                logger.debug(f"{stock} 刚突破前高{breakthrough_pct:.2f}%，涨幅{price_change_pct:.2f}%≥{min_increase}%，B2不触发")
                        
                        # 如果未触发保护，执行B2卖出
                        if not should_skip_b2:
                            # 获取买入时的策略类型
                            buy_strategy = self.stock_strategy_types.get(stock, "未知策略")
                            
                            sell_reason = (f"({buy_strategy}) 条件B2-{stock_type_name}放量滞涨: 突破前高"
                                         f"{previous_high:.2f} + 当前{current_price:.2f} + "
                                         f"量比{volume_ratio:.2f} + 涨幅{price_change_pct:+.2f}%<{params_b2['price_increase_limit']:.1f}%")
                            
                            # 获取持仓信息
                            position = self.position_manager.get_position(stock)
                            position_log_info = None
                            if position:
                                position_log_info = {
                                    'buy_price': position.buy_price,
                                    'current_price': current_price,
                                    'shares': position.shares,
                                    'sell_ratio': 1.0  # 条件B2全部清仓
                                }
                            
                            # 使用统一的日志输出方法
                            details = {
                                '当前价格': f"{current_price:.2f} | 前期高位: {previous_high:.2f} | 突破幅度: {(current_price/previous_high-1)*100:.2f}%",
                                '当日涨幅': f"{price_change_pct:+.2f}% (滞涨<{params_b2['price_increase_limit']:.1f}%) | 量比: {volume_ratio:.2f} (放量标准>{params_b2['volume_ratio']})",
                                '成交量': f"{self.format_volume(current_volume)} | 20日均量×{params_b2['vol_ma20_ratio']}: {self.format_volume(vol_ma20*params_b2['vol_ma20_ratio'])}",
                                '判断': "放量但滞涨，量价背离信号"
                            }
                            # ⚠️ 注释掉策略层日志输出，避免与回测系统的验证日志重复
                            # self.log_sell_signal(stock, buy_strategy, sell_reason, stock_type_name, market_cap, details, position_log_info)
                            
                            sell_signal = {
                                'should_sell': True,
                                'reason': sell_reason,
                                'sell_ratio': 1.0,  # 条件B2全部清仓
                                'details': {
                                    'current_price': current_price,
                                    'previous_high': previous_high,
                                    'volume_ratio': volume_ratio,
                                    'price_position': price_position,  # 添加价格位置，用于技术形态指标输出
                                    'price_change_pct': price_change_pct,
                                    'market_cap': market_cap,
                                    'stock_type': stock_type_name,
                                    'signal_type': '高位放量滞涨',
                                    'breakthrough': True
                                }
                            }
                            
                            # 条件B2信号（放量滞涨，全仓卖出）
                            
                            # 使用技术形态增强信号（融合模式）
                            sell_signal = self._enhance_signal_with_technical_patterns(stock, sell_signal, current_price, today_data)
                            return sell_signal
                    
                    # 情况3：放量大涨（涨幅>=5%），可能是真突破，继续持有
                    elif price_change_pct >= 5.0:
                        logger.debug(f"{stock} 突破前高放量大涨(量比{volume_ratio:.2f}, 涨幅{price_change_pct:+.2f}%)，可能是真突破，继续持有")
                        # 不卖出，继续持有
            
            # ========== 条件C：跌破5日线 + 放量确认（差异化标准）==========
            if len(closes) < 5:
                return {'should_sell': False, 'reason': '数据不足计算均线'}
            
            # 使用预计算的5日均线
            ma5 = today_data.get('ma5')
            if ma5 is None:
                return {'should_sell': False, 'reason': 'MA5未预计算'}
            
            below_ma5 = current_price < ma5
            
            # ===== 新增：跌破幅度要求（至少0.5%）=====
            if below_ma5:
                break_pct = (ma5 - current_price) / ma5 * 100
                if break_pct < 0.5:  # 跌破幅度小于0.5%，视为微小波动
                    logger.debug(f"{stock} 跌破5日线幅度{break_pct:.2f}% < 0.5%，暂不触发条件C")
                    return {'should_sell': False, 'reason': '跌破幅度不足0.5%'}
            else:
                return {'should_sell': False, 'reason': '未跌破5日均线且非高位缩量/放量情况'}
            
            # 获取该类型股票的放量标准
            params_c = self.sell_params['break_ma5'][stock_type]
            
            # 差异化判断：大盘1.2/中盘1.5/小盘1.8（✅已去掉"成交量>前一日"的冗余条件）
            # 量比 > X 且 成交量 > 20日均量 × Y
            # 卖出不使用时间因子：跌破支撑位需立即确认，保持严格
            if (volume_ratio > params_c['volume_ratio'] and 
                current_volume > vol_ma20 * params_c['vol_ma20_ratio']):
                
                # 获取买入时的策略类型
                buy_strategy = self.stock_strategy_types.get(stock, "未知策略")
                
                sell_reason = (f"({buy_strategy}) 条件C-{stock_type_name}跌破5日线放量确认: 当前{current_price:.2f}<MA5({ma5:.2f}) + "
                             f"量比{volume_ratio:.2f}>{params_c['volume_ratio']} + "
                             f"成交量{self.format_volume(current_volume)}>20日均量×{params_c['vol_ma20_ratio']}")
                
                # 获取持仓信息
                position = self.position_manager.get_position(stock)
                position_log_info = None
                if position:
                    position_log_info = {
                        'buy_price': position.buy_price,
                        'current_price': current_price,
                        'shares': position.shares,
                        'sell_ratio': 1.0  # 条件C全部清仓
                    }
                
                # 使用统一的日志输出方法
                details = {
                    '当前价格': f"{current_price:.2f} | 5日线: {ma5:.2f} | 跌破幅度: {(ma5-current_price)/ma5*100:.2f}%",
                    '量比': f"{volume_ratio:.2f} > {params_c['volume_ratio']} (放量标准)",
                    '成交量': f"{self.format_volume(current_volume)} > {self.format_volume(vol_ma20*params_c['vol_ma20_ratio'])} (20日均量×{params_c['vol_ma20_ratio']})"
                }
                # 卖出信号触发（回测系统会统一处理）
                
                sell_signal = {
                    'should_sell': True,
                    'reason': sell_reason,
                    'sell_ratio': 1.0,  # 条件C全部清仓
                    'details': {
                        'current_price': current_price,
                        'ma5': ma5,
                        'volume_ratio': volume_ratio,
                        'price_position': price_position,  # 添加价格位置，用于技术形态指标输出
                        'current_volume': current_volume,
                        'market_cap': market_cap,
                        'stock_type': stock_type_name,
                        'signal_type': '跌破5日线'
                    }
                }
                
                # 条件C信号（跌破5日线，全仓卖出）
                
                # 使用技术形态增强信号（融合模式）
                sell_signal = self._enhance_signal_with_technical_patterns(stock, sell_signal, current_price, today_data)
                return sell_signal
            
            # ========== 条件E：趋势转弱信号（独立触发）==========
            trend_sell_result = self.check_sell_trend_signal(stock, closes, current_price, volume_ratio)
            if trend_sell_result['should_sell']:
                # 获取买入时的策略类型
                buy_strategy = self.stock_strategy_types.get(stock, "未知策略")
                
                sell_reason = f"({buy_strategy}) {trend_sell_result['reason']}"
                
                # 趋势转弱，全仓卖出
                sell_signal = {
                    'should_sell': True,
                    'reason': sell_reason,
                    'sell_type': 'full',  # 全仓卖出
                    'position_ratio': 1.0,
                    'details': {
                        'stock_type': stock_type_name,
                        'signal_type': '趋势转弱',
                        'trend_details': trend_sell_result.get('details', {})
                    }
                }
                
                # 趋势转弱信号不需要技术形态增强（已经是独立信号）
                logger.debug(f"✅ {stock} 触发条件E（趋势转弱）: {sell_reason}")
                return sell_signal
            
            # 所有卖出条件都不满足
            return {'should_sell': False, 'reason': '未触发任何卖出条件'}
            
        except Exception as e:
            logger.error(f"检查{stock}卖出条件时出错: {str(e)}")
            return {'should_sell': False, 'reason': f'检查异常: {str(e)}'}

    def run(self):
        """策略运行主循环"""
        logger.info("策略开始运行...")
        
        # 模拟持仓记录（实盘中可以通过XtQuantTrader获取）
        self.mock_positions = {}  # {stock: {'buy_date': 'YYYYMMDD', 'buy_price': float, 'shares': int}}
        
        while True:
            try:
                # 检查是否在交易时间内
                if not self.check_market_status():
                    # 非交易时间，等待一段时间
                    time.sleep(60)  # 非交易时间等待60秒
                    continue
                
                # 每天开盘后检查持仓股票的卖出信号
                current_date = time.strftime('%Y%m%d')
                
                # 检查持仓股票的卖出条件
                positions_to_sell = []
                for stock in list(self.mock_positions.keys()):
                    try:
                        sell_signal = self.check_sell_conditions(stock)
                        if sell_signal['should_sell']:
                            positions_to_sell.append((stock, sell_signal['reason']))
                            logger.info(f"***** 卖出信号: {stock} - {sell_signal['reason']} *****")
                    except Exception as e:
                        logger.error(f"检查{stock}卖出条件失败: {str(e)}")
                
                # 执行卖出（在实盘中这里可以调用XtQuantTrader.order_stock）
                for stock, reason in positions_to_sell:
                    if stock in self.mock_positions:
                        position = self.mock_positions[stock]
                        current_price = self.today_data.get(stock, {}).get('last', 0)
                        logger.info(f"模拟卖出: {stock} @ {current_price:.2f} - {reason}")
                        # 实盘中在这里调用: self.xt_trader.order_stock(...)
                        del self.mock_positions[stock]
                
                # 每天收盘后重置触发状态
                now = time.localtime()
                if now.tm_hour == 15 and now.tm_min >= 30:
                    logger.info(f"{time.strftime('%H:%M:%S')} 收盘后清空去重集合")
                    # 清空去重集合
                    self.buy_stocks.clear()
                    self.sell_stocks.clear()
                    logger.info("已清空今日去重集合")

                    # 等待到第二天9:15
                    next_day = time.time() + (24 * 3600) - (now.tm_hour * 3600 + now.tm_min * 60 + now.tm_sec) + (9 * 3600 + 15 * 60)
                    wait_seconds = next_day - time.time()
                    if wait_seconds > 0:
                        logger.info(f"等待至次日9:15，预计等待时间: {int(wait_seconds/3600)}小时{int(wait_seconds%3600/60)}分钟")
                        time.sleep(wait_seconds)
                    
                    # 次日开盘前重新下载最新数据
                    logger.info("次日开盘前重新下载数据")
                    self.download_history_data()
                    logger.info("新交易日开始")
                else:
                    # 极短时间等待，主要靠回调驱动
                    time.sleep(0.1)
            except Exception as e:
                logger.error(f"主循环错误: {str(e)}")
                time.sleep(10)

    def analyze_volume_price_relationship(self, stock, today_data, hist_np):
        """
        全面的量价关系分析 - 整合九种经典量价模式、振幅分析和时间因子
        """
        try:
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')
            
            # 至少需要20天数据进行基本分析
            if closes is None or volumes is None or len(closes) < 20:
                return {'valid': False, 'reason': '历史数据不足(至少需要20个交易日)'}
            
            current_price = today_data['last']
            current_volume = today_data['volume']
            last_close = today_data.get('lastClose', closes[-1] if len(closes) > 0 else current_price)
            high_price = today_data.get('high', current_price)
            low_price = today_data.get('low', current_price)
            
            # 1. 价格位置分析（使用配置文件参数）
            position_config = self.strategy_config.get('price_position_analysis', {})
            max_analysis_days = position_config.get('analysis_days', 60)
            low_threshold = position_config.get('low_threshold', 0.30)
            mid_threshold = position_config.get('mid_threshold', 0.80)
            
            analysis_days = min(max_analysis_days, len(closes))  # 最多配置的天数，不足则用实际天数
            recent_high = float(np.max(closes[-analysis_days:]))
            recent_low = float(np.min(closes[-analysis_days:]))
            price_range = recent_high - recent_low
            
            if price_range <= 0:
                return {'valid': False, 'reason': '价格区间无效'}
            
            price_position = (current_price - recent_low) / price_range
            
            if price_position <= low_threshold:
                position = "低位"
            elif price_position <= mid_threshold:
                position = "中位"
            else:
                position = "高位"
            
            # 2. 振幅分析（根据测试结果添加）
            amplitude = 0.0
            if last_close > 0 and high_price > 0 and low_price > 0:
                amplitude = (high_price - low_price) / last_close * 100
            
            # 振幅判断
            if amplitude < 2.0:
                amplitude_status = "低振幅"
            elif amplitude < 5.0:
                amplitude_status = "正常振幅"
            else:
                amplitude_status = "高振幅"
            
            # 3. 量能分析（优化版：差异化标准）
            vol_ma5 = float(np.mean(volumes[-5:]))
            vol_ma10 = float(np.mean(volumes[-10:]))
            vol_ma20 = float(np.mean(volumes[-20:])) if len(volumes) >= 20 else vol_ma10  # 20日均量
            
            # 量比计算：统一调用get_volume_ratio（已优化，回测模式会优先使用today_data['volRatio']）
            volume_ratio_optimized = self.get_volume_ratio(stock)
            
            # 传统量比计算（相对于5日均量，作为备选）
            traditional_ratio = current_volume / vol_ma5 if vol_ma5 > 0 else 1.0
            
            # 优先使用优化的量比，如果获取失败则使用传统计算
            final_volume_ratio = volume_ratio_optimized if volume_ratio_optimized > 0 else traditional_ratio
            
            # 获取股票类型（大中小盘）
            stock_type = self.get_stock_type(stock)
            if stock_type == 'unknown':
                stock_type = 'mid'  # 默认使用中盘股标准
            
            # 获取差异化的量能分类标准
            vol_config = self.buy_params['normal_strategy']['volume_classification'].get(stock_type, 
                         self.buy_params['normal_strategy']['volume_classification']['mid'])
            
            # 量能状态分类（使用差异化标准）
            if final_volume_ratio >= vol_config['strong']:
                volume_status = "放量"
            elif final_volume_ratio >= vol_config['moderate']:
                volume_status = "温和放量"
            elif final_volume_ratio >= vol_config['normal']:
                volume_status = "正常量"
            elif final_volume_ratio >= vol_config['shrink']:
                volume_status = "温和缩量"
            else:
                volume_status = "缩量"
            
            # 双重量能验证（v3.4升级版）
            # 使用独立的验证器模块，支持三重验证
            vol_ma20_pass = False
            validation_reason = ""
            
            if self.volume_validator is not None:
                # 使用新的双重验证器
                try:
                    # 准备股票数据
                    stock_data = {
                        'volume': current_volume,
                        'volume_ma5': vol_ma5,
                        'volume_ma10': vol_ma10,
                        'volume_ma20': vol_ma20,
                        'volumes': volumes  # 用于第三重验证
                    }
                    
                    # 获取市值类型映射
                    market_cap_type = {
                        'large': 'large_cap',
                        'mid': 'mid_cap',
                        'small': 'small_cap'
                    }.get(stock_type, 'mid_cap')
                    
                    # 获取当前时间
                    time_tag = today_data.get('timetag', '')
                    current_time = None
                    if time_tag:
                        try:
                            current_time = datetime.strptime(time_tag, '%Y-%m-%d %H:%M:%S')
                        except:
                            pass
                    
                    # 调用验证器
                    validation_result = self.volume_validator.validate_volume_conditions(
                        stock_data=stock_data,
                        market_cap_type=market_cap_type,
                        current_time=current_time,
                        market_environment=None  # 可选：未来可添加市场环境数据
                    )
                    
                    # 获取验证结果
                    vol_ma20_pass = validation_result['passed']
                    volume_status = validation_result['volume_status']  # 更新量能状态
                    validation_reason = validation_result['message']
                    
                    # 记录详细验证信息（未通过时）
                    if not vol_ma20_pass:
                        details = validation_result['details']
                        logger.debug(f"{stock} 双重验证未通过: {validation_reason}")
                        logger.debug(f"  详情: {details}")
                    
                except Exception as e:
                    logger.warning(f"{stock} 双重验证异常，回退到原有逻辑: {e}")
                    # 回退到原有逻辑
                    vol_ma20_pass, validation_reason = self.check_volume_validation_time_aware(
                        stock, today_data, hist_np, volume_status, stock_type
                    )
            else:
                # 回退到原有逻辑（兼容性）
                vol_ma20_pass, validation_reason = self.check_volume_validation_time_aware(
                    stock, today_data, hist_np, volume_status, stock_type
                )
                
                # 记录验证详情（仅在未通过时）
                if not vol_ma20_pass:
                    logger.debug(f"{stock} 20日均量验证: {validation_reason}")
            
            # 4. 九种经典量价模式分析
            volume_price_pattern = f"{position}{volume_status}"
            
            # 九种模式及其技术含义
            pattern_analysis = {
                "低位放量": {"signal": "强烈看多", "valid": True, "desc": "低位量增价涨，主力入场"},
                "低位温和放量": {"signal": "看多", "valid": True, "desc": "低位温和量增，稳健上攻"},
                "低位正常量": {"signal": "中性偏多", "valid": True, "desc": "低位正常量，等待量能放大"},
                "低位温和缩量": {"signal": "等待", "valid": False, "desc": "低位缩量，等待量能配合"},
                "低位缩量": {"signal": "等待", "valid": False, "desc": "低位缩量，不宜操作"},
                
                "中位放量": {"signal": "谨慎看多", "valid": True, "desc": "中位放量，可能突破或分化"},
                "中位温和放量": {"signal": "中性", "valid": True, "desc": "中位温和放量，需结合趋势"},
                "中位正常量": {"signal": "中性", "valid": True, "desc": "中位正常量，保持关注"},
                "中位温和缩量": {"signal": "洗盘", "valid": True, "desc": "中位缩量，可能是洗盘"},
                "中位缩量": {"signal": "洗盘", "valid": True, "desc": "中位缩量，典型洗盘特征"},
                
                "高位放量": {"signal": "警惕", "valid": False, "desc": "高位放量，可能见顶"},
                "高位温和放量": {"signal": "警惕", "valid": False, "desc": "高位温和放量，谨慎操作"},
                "高位正常量": {"signal": "中性偏空", "valid": False, "desc": "高位正常量，注意风险"},
                "高位温和缩量": {"signal": "空方控盘", "valid": False, "desc": "高位缩量，空方控盘"},
                "高位缩量": {"signal": "空方控盘", "valid": False, "desc": "高位缩量，明显空方信号"}
            }
            
            pattern_info = pattern_analysis.get(volume_price_pattern, 
                {"signal": "未知", "valid": False, "desc": "未定义模式"})
            
            # 5. 综合判断（结合振幅分析 + 20日均量验证）
            base_valid = pattern_info['valid']
            
            # 振幅修正
            if amplitude_status == "高振幅" and position == "高位":
                base_valid = False  # 高位高振幅风险大
            elif amplitude_status == "低振幅" and volume_status in ["缩量", "温和缩量"]:
                base_valid = False  # 低振幅缩量缺乏动能
            
            # 20日均量验证修正（强烈放量和温和放量必须通过20日均量验证）
            if not vol_ma20_pass and volume_status in ["放量", "温和放量"]:
                base_valid = False  # 未通过20日均量验证
            
            # 6. 时间因子修正（量比计算已优化，无需显示此提示）
            # 综合评分计算（使用配置文件参数）
            scoring_config = self.strategy_config.get('normal', {}).get('scoring', {})
            base_score = scoring_config.get('base_score', 60)
            volume_score = scoring_config.get('volume_score', 20)
            amplitude_score = scoring_config.get('amplitude_score', 10)
            position_score = scoring_config.get('position_score', 10)
            pass_threshold = scoring_config.get('pass_threshold', 70)
            
            score = 0
            if base_valid:
                score += base_score  # 基础分
                if volume_status in ["放量", "温和放量"]:
                    score += volume_score  # 量能加分
                if amplitude_status == "正常振幅":
                    score += amplitude_score  # 振幅加分
                if position == "低位":
                    score += position_score  # 位置加分
            
            final_valid = score >= pass_threshold  # 配置的通过标准
            
            return {
                'valid': final_valid,
                'position': position,
                'volume_status': volume_status,
                'amplitude': amplitude,
                'amplitude_status': amplitude_status,
                'volume_ratio': final_volume_ratio,
                'volume_ratio_optimized': volume_ratio_optimized,
                'traditional_ratio': traditional_ratio,
                'pattern': volume_price_pattern,
                'signal': pattern_info['signal'],
                'description': pattern_info['desc'],
                'score': score,
                'stock_type': stock_type,
                'vol_ma20_pass': vol_ma20_pass,
                'vol_ma20': vol_ma20,
                'vol_ma20_ratio': current_volume / vol_ma20 if vol_ma20 > 0 else 0,
                'reason': f'{volume_price_pattern}(振幅{amplitude:.2f}%,评分{score})'
            }
            
        except Exception as e:
            logger.debug(f"{stock} 量价分析失败: {str(e)}")  # 降为debug级别
            return {'valid': False, 'reason': f'分析异常: {str(e)}'}
    
    def detect_volume_price_divergence(self, stock, today_data, hist_np, lookback_days=5):
        """
        检测量价背离现象
        传统量价关系要求“量价齐升”，即价格上升时成交量也要同步放大
        背离情况：
        1. 价格上升趋势 + 成交量下降趋势 = 量价背离（疑似顶部信号）
        2. 价格下降趋势 + 成交量上升趋势 = 量价背离（疑似底部信号）
        """
        try:
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')
            
            if closes is None or volumes is None or len(closes) < lookback_days:
                return {'valid': True, 'reason': f'历史数据不足({len(closes) if closes is not None else 0}天<{lookback_days}天),跳过背离检测'}
            
            current_price = today_data['last']
            current_volume = today_data['volume']
            
            # 分析价格趋势（最近5个交易日）
            price_trend = self.analyze_price_trend(closes, current_price, lookback_days)
            
            # 分析成交量趋势（最近5个交易日）
            volume_trend = self.analyze_volume_trend(volumes, current_volume, lookback_days)
            
            # 检测背离情况
            price_is_uptrend = price_trend['is_uptrend']
            price_slope = price_trend.get('slope', 0)
            volume_is_healthy = volume_trend['is_healthy']
            volume_slope = volume_trend.get('slope', 0)
            
            # 背离判断逻辑
            divergence_detected = False
            divergence_type = None
            risk_level = "低风险"
            
            # 1. 价格上升趋势 + 成交量下降趋势 = 顶部背离险象
            if price_is_uptrend and price_slope > 0:
                # 成交量明显下降
                if volume_slope < -abs(price_slope) * 0.5:  # 成交量下降幅度超过价格上升幅度癇50%
                    divergence_detected = True
                    divergence_type = "顶部背离险象"
                    risk_level = "高风险"
                # 成交量轻微下降但不严重
                elif volume_slope < 0 and not volume_is_healthy:
                    divergence_detected = True
                    divergence_type = "轻微顶部背离"
                    risk_level = "中风险"
            
            # 2. 价格下降趋势 + 成交量上升趋势 = 底部背离险象（相对乐观）
            elif not price_is_uptrend and price_slope < 0:
                # 成交量明显上升
                if volume_slope > abs(price_slope) * 0.5:  # 成交量上升幅度超过价格下降幅度癇50%
                    divergence_detected = True
                    divergence_type = "底部背离险象"
                    risk_level = "低风险"  # 底部背离可能是好事
            
            # 结果判断
            if divergence_detected:
                # 顶部背离需要警惕，不通过筛选
                if divergence_type in ["顶部背离险象", "轻微顶部背离"]:
                    return {
                        'valid': False,
                        'divergence_detected': True,
                        'divergence_type': divergence_type,
                        'risk_level': risk_level,
                        'price_trend': price_trend['trend_description'],
                        'volume_trend': volume_trend['trend_description'],
                        'reason': f'检测到{divergence_type}，价格上升但成交量下降，风险级别: {risk_level}'
                    }
                # 底部背离可以通过，但需记录
                else:
                    return {
                        'valid': True,
                        'divergence_detected': True,
                        'divergence_type': divergence_type,
                        'risk_level': risk_level,
                        'price_trend': price_trend['trend_description'],
                        'volume_trend': volume_trend['trend_description'],
                        'reason': f'检测到{divergence_type}，可能是底部信号，允许通过'
                    }
            else:
                # 没有检测到背离，正常通过
                return {
                    'valid': True,
                    'divergence_detected': False,
                    'divergence_type': None,
                    'risk_level': "无风险",
                    'price_trend': price_trend['trend_description'],
                    'volume_trend': volume_trend['trend_description'],
                    'reason': f'量价趋势正常，价格趋势: {price_trend["trend_description"]}, 成交量趋势: {volume_trend["trend_description"]}'
                }
                
        except Exception as e:
            logger.debug(f"{stock} 量价背离检测异常: {str(e)}")
            return {'valid': True, 'reason': f'背离检测异常，默认通过: {str(e)}'}

    def analyze_price_trend(self, closes, current_price, lookback_days=10):
        """
        分析价格趋势是否逐步上升
        根据用户建议：当前的价格的趋势是逐步上升
        """
        try:
            if len(closes) < lookback_days:
                return {'is_uptrend': False, 'trend_description': '数据不足', 'trend_strength': 0}
            
            # 取最近N天的数据
            recent_closes = list(closes[-lookback_days:]) + [current_price]
            
            # 计算趋势强度（线性回归斜率）
            x = np.arange(len(recent_closes))
            y = np.array(recent_closes)
            slope = np.polyfit(x, y, 1)[0]  # 一次项系数即为斜率
            
            # 统计上涨天数
            up_days = 0
            for i in range(1, len(recent_closes)):
                if recent_closes[i] > recent_closes[i-1]:
                    up_days += 1
            
            up_ratio = up_days / (len(recent_closes) - 1)
            
            # 综合判断：斜率为正即可
            is_uptrend = slope > 0
            trend_strength = min(1.0, max(0.0, (slope * 100 + up_ratio) / 2))  # 归一化强度
            
            if is_uptrend:
                if trend_strength >= 0.8:
                    desc = "强势上升趋势"
                elif trend_strength >= 0.6:
                    desc = "稳健上升趋势"
                else:
                    desc = "温和上升趋势"
            else:
                desc = f"趋势不符合要求(斜率{slope:.4f},上涨比例{up_ratio:.2f})"
            
            return {
                'is_uptrend': is_uptrend,
                'trend_description': desc,
                'trend_strength': trend_strength,
                'slope': slope,
                'up_ratio': up_ratio
            }
            
        except Exception as e:
            logger.debug(f"价格趋势分析异常: {e}")
            return {'is_uptrend': False, 'trend_description': '分析异常', 'trend_strength': 0}
    
    def analyze_volume_trend(self, volumes, current_volume, lookback_days=10):
        """
        分析成交量趋势：同步上升或稳定
        根据用户建议：成交量也应该同步的上升或者上下浮动不是特别大
        """
        try:
            if len(volumes) < lookback_days:
                return {'is_healthy': False, 'trend_description': '数据不足', 'volume_strength': 0}
            
            # 取最近N天的数据
            recent_volumes = list(volumes[-lookback_days:]) + [current_volume]
            
            # 计算成交量变化的标准差（衡量波动）
            volume_std = np.std(recent_volumes)
            volume_mean = np.mean(recent_volumes)
            cv = volume_std / volume_mean if volume_mean > 0 else 999  # 变异系数
            
            # 计算成交量趋势（线性回归）
            x = np.arange(len(recent_volumes))
            y = np.array(recent_volumes)
            slope = np.polyfit(x, y, 1)[0]
            
            # 判断成交量是否健康
            is_stable = cv <= 1.0  # 变异系数要求
            is_ascending = slope >= -volume_mean * 0.1  # 允许轻微下降
            is_healthy = is_ascending and is_stable
            volume_strength = min(1.0, max(0.0, float((1 - cv + (slope / volume_mean if volume_mean > 0 else 0)) / 2)))
            
            if is_healthy:
                if slope > volume_mean * 0.1:  # 斜率相对较大
                    desc = "量能同步上升"
                else:
                    desc = "量能相对稳定"
            else:
                if not is_ascending:
                    desc = "量能呈下降趋势"
                else:
                    desc = f"量能波动过大(变异系数{cv:.2f})"
            
            return {
                'is_healthy': is_healthy,
                'trend_description': desc,
                'volume_strength': volume_strength,
                'cv': cv,
                'slope': slope
            }
            
        except Exception as e:
            logger.debug(f"成交量趋势分析异常: {e}")
            return {'is_healthy': False, 'trend_description': '分析异常', 'volume_strength': 0}
    
    # ============ 统一指标预计算方法 ============
    
    def precalculate_indicators(self, stock, today_data, hist_np):
        """
        统一预计算所有技术指标，存储到today_data中
        
        优势：
        1. 统一数据源，避免today_data不一致问题
        2. 便于调试和排查问题
        3. 指标计算逻辑集中管理
        
        预计算指标包括：
        - 基础指标：MA5/MA10/MA20、量比、价格位置
        - 趋势指标：价格趋势、MA5趋势、MA10趋势、均线发散度
        - 辅助指标：上影线比例等
        
        Returns:
            bool: 是否成功计算（数据充足）
        """
        try:
            closes = hist_np.get('close')
            volumes = hist_np.get('volume')
            
            if closes is None or volumes is None:
                return False
            
            current_price = today_data['last']
            current_volume = today_data['volume']
            
            # ========== 1. 基础均线指标 ==========
            if len(closes) >= 5:
                today_data['ma5'] = float(np.mean(list(closes[-4:]) + [current_price]))
            if len(closes) >= 10:
                today_data['ma10'] = float(np.mean(list(closes[-9:]) + [current_price]))
            if len(closes) >= 20:
                today_data['ma20'] = float(np.mean(list(closes[-19:]) + [current_price]))
            
            # ========== 2. 量比指标 ==========
            volume_ratio = self.get_volume_ratio(stock)
            today_data['volume_ratio'] = volume_ratio
            
            # 20日均量
            if len(volumes) >= 20:
                today_data['vol_ma20'] = float(np.mean(volumes[-20:]))
            
            # ========== 3. 价格位置指标 ==========
            if len(closes) >= 30:
                price_position_result = self._calculate_price_position(stock, today_data, closes)
                if price_position_result.get('valid', False):
                    today_data['price_position'] = price_position_result['position']
                    today_data['price_position_low'] = price_position_result['low']
                    today_data['price_position_high'] = price_position_result['high']
            
            # ========== 4. 趋势指标==========
            
                buy_config = self.trend_filter_config.get('buy', {})
                
                # 4.1 价格趋势
                price_trend_config = buy_config.get('price_trend', {})
            if len(closes) >= 10:
                    lookback_days = price_trend_config.get('lookback_days', 10)
                    price_trend = self.analyze_price_trend(closes, current_price, lookback_days)
                    today_data['price_trend'] = price_trend
                    today_data['price_trend_slope'] = price_trend.get('slope', 0)
                    today_data['price_trend_strength'] = price_trend.get('trend_strength', 0)
                    today_data['price_is_uptrend'] = price_trend.get('is_uptrend', False)
                
            # 4.2 MA5趋势（总是计算）
            ma5_trend_config = buy_config.get('ma5_trend', {})
            if len(closes) >= 10:
                lookback_days = ma5_trend_config.get('lookback_days', 5)
                ma5_trend = self.calculate_ma_slope(closes, 5, lookback_days, current_price)
                today_data['ma5_trend'] = ma5_trend
                today_data['ma5_slope'] = ma5_trend.get('slope', 0)
                today_data['ma5_slope_pct'] = ma5_trend.get('slope_pct', 0)
                today_data['ma5_is_uptrend'] = ma5_trend.get('is_uptrend', False)
            
            # 4.3 MA10趋势（总是计算）
            ma10_trend_config = buy_config.get('ma10_trend', {})
            if len(closes) >= 15:
                lookback_days = ma10_trend_config.get('lookback_days', 5)
                ma10_trend = self.calculate_ma_slope(closes, 10, lookback_days, current_price)
                today_data['ma10_trend'] = ma10_trend
                today_data['ma10_slope'] = ma10_trend.get('slope', 0)
                today_data['ma10_slope_pct'] = ma10_trend.get('slope_pct', 0)
                today_data['ma10_is_uptrend'] = ma10_trend.get('is_uptrend', False)
            
            # 4.4 均线发散度（总是计算）
            ma_divergence_config = buy_config.get('ma_divergence', {})
            if len(closes) >= 20:
                stock_type = self.get_stock_type(stock)
                divergence = self.check_ma_divergence(closes, current_price, stock_type)
                today_data['ma_divergence'] = divergence
                today_data['ma_spacing_pct'] = divergence.get('spacing_pct', 0)
                today_data['ma_is_divergent'] = divergence.get('is_divergent', False)
            
            # ========== 5. K线形态指标 ==========
            high = today_data.get('high', current_price)
            low = today_data.get('low', current_price)
            total_range = high - low
            upper_shadow = high - current_price
            lower_shadow = current_price - low
            
            today_data['upper_ratio'] = upper_shadow / total_range if total_range > 0 else 0
            today_data['lower_ratio'] = lower_shadow / total_range if total_range > 0 else 0
            today_data['body_ratio'] = abs(current_price - today_data.get('lastClose', current_price)) / total_range if total_range > 0 else 0
            
            # ========== 6. 股票分类 ==========
            stock_type = self.get_stock_type(stock)
            today_data['stock_type'] = stock_type
            today_data['market_cap'] = self.get_stock_market_cap(stock)
            
            # 添加计算时间戳（用于调试）
            from datetime import datetime
            today_data['_indicators_calculated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            
            return True
            
        except Exception as e:
            logger.error(f"{stock} 指标预计算异常: {e}")
            return False
    
    # ============ 趋势判断函数（硬指标）============
    
    def calculate_ma_slope(self, closes, ma_period, lookback_days, current_price=None):
        """
        计算均线的斜率（趋势方向）
        
        Args:
            closes: 历史收盘价序列
            ma_period: 均线周期（如5/10/20）
            lookback_days: 回溯天数（计算最近N天的MA值来判断趋势）
            current_price: 当前价格（可选，如果提供则包含在最新MA计算中）
        
        Returns:
            {
                'slope': 斜率值（正数=向上，负数=向下，0=横盘）,
                'slope_pct': 斜率百分比（相对于均线值的%），
                'is_uptrend': 是否向上趋势,
                'is_downtrend': 是否向下趋势,
                'description': 趋势描述
            }
        """
        try:
            if len(closes) < ma_period + lookback_days:
                return {
                    'slope': 0,
                    'slope_pct': 0,
                    'is_uptrend': False,
                    'is_downtrend': False,
                    'description': f'数据不足({len(closes)}<{ma_period+lookback_days})'  # 显示实际数据量
                }
            
            # 计算最近N天的MA值
            ma_values = []
            for i in range(lookback_days):
                idx = -(lookback_days - i)
                # 修复：当idx=-1时，切片结束索引应该为None（表示到末尾）
                if idx == -1:
                    # 最后一天：从倒数第ma_period个到末尾
                    ma_slice = closes[-ma_period:]
                else:
                    # 其他天：正常切片
                    ma_slice = closes[idx - ma_period + 1:idx + 1]
                
                if len(ma_slice) < ma_period:
                    # 数据不足，返回失败
                    return {
                        'slope': 0,
                        'slope_pct': 0,
                        'is_uptrend': False,
                        'is_downtrend': False,
                        'description': f'数据不足({len(closes)}<{ma_period+lookback_days})'  # 显示实际数据量
                    }
                ma_val = float(np.mean(ma_slice))
                ma_values.append(ma_val)
            
            # 如果提供了当前价格，计算最新的MA值
            if current_price is not None:
                latest_closes = list(closes[-(ma_period-1):]) + [current_price]
                latest_ma = float(np.mean(latest_closes))
                ma_values.append(latest_ma)
            
            # 计算斜率（线性回归）
            x = np.arange(len(ma_values))
            y = np.array(ma_values)
            slope = np.polyfit(x, y, 1)[0]  # 绝对斜率
            
            # 计算斜率百分比（相对于MA值的变化率）
            first_ma = ma_values[0]
            slope_pct = (slope / first_ma * 100) if first_ma > 0 else 0
            
            # 判断趋势方向
            is_uptrend = slope > 0
            is_downtrend = slope < 0
            
            # 生成描述
            if is_uptrend:
                if slope_pct >= 1.0:
                    desc = f"MA{ma_period}强势上升(+{slope_pct:.2f}%/天)"
                elif slope_pct >= 0.3:
                    desc = f"MA{ma_period}稳健上升(+{slope_pct:.2f}%/天)"
                else:
                    desc = f"MA{ma_period}温和上升(+{slope_pct:.2f}%/天)"
            elif is_downtrend:
                if slope_pct <= -1.0:
                    desc = f"MA{ma_period}快速下降({slope_pct:.2f}%/天)"
                elif slope_pct <= -0.3:
                    desc = f"MA{ma_period}明显下降({slope_pct:.2f}%/天)"
                else:
                    desc = f"MA{ma_period}轻微下降({slope_pct:.2f}%/天)"
            else:
                desc = f"MA{ma_period}横盘震荡"
            
            return {
                'slope': float(slope),
                'slope_pct': float(slope_pct),
                'is_uptrend': is_uptrend,
                'is_downtrend': is_downtrend,
                'description': desc,
                'ma_values': ma_values
            }
            
        except Exception as e:
            logger.debug(f"MA斜率计算异常: {e}")
            return {
                'slope': 0,
                'slope_pct': 0,
                'is_uptrend': False,
                'is_downtrend': False,
                'description': '计算异常'
            }
    
    def check_ma_divergence(self, closes, current_price, stock_type):
        """
        检查均线发散度（MA5相对MA20的距离）
        
        Args:
            closes: 历史收盘价
            current_price: 当前价格
            stock_type: 股票类型（large/mid/small）
        
        Returns:
            {
                'spacing_pct': 间距百分比,
                'threshold_pct': 阈值,
                'is_divergent': 是否达到发散要求,
                'description': 描述
            }
        """
        try:
            if len(closes) < 20:
                return {
                    'spacing_pct': 0,
                    'threshold_pct': 0,
                    'is_divergent': False,
                    'description': '数据不足'
                }
            
            # 计算MA5和MA20（包含当前价格）
            ma5 = float(np.mean(list(closes[-4:]) + [current_price]))
            ma20 = float(np.mean(list(closes[-19:]) + [current_price]))
            
            # 计算间距百分比
            spacing_pct = ((ma5 - ma20) / ma20 * 100) if ma20 > 0 else 0
            
            # 获取阈值（从配置读取）
            config = self.trend_filter_config.get('buy', {}).get('ma_divergence', {})
            min_threshold_map = config.get('min_spacing_pct', {'large': 1.0, 'mid': 1.5, 'small': 2.0})
            min_threshold = min_threshold_map.get(stock_type, 1.5)
            
            # 获取最大阈值（新增：防止涨多了）
            max_threshold_map = config.get('max_spacing_pct', {'large': 5.0, 'mid': 6.0, 'small': 7.0})
            max_threshold = max_threshold_map.get(stock_type, 999.0)  # 默认不限制
            
            # 判断是否达到发散要求（下限 + 上限）
            is_divergent = spacing_pct >= min_threshold and spacing_pct <= max_threshold
            
            # 生成描述
            if spacing_pct < min_threshold:
                desc = f"均线发散不足({spacing_pct:.2f}% < {min_threshold:.2f}%)"
            elif spacing_pct > max_threshold:
                desc = f"均线发散过大({spacing_pct:.2f}% > {max_threshold:.2f}%)，可能涨多了"
            else:
                desc = f"均线发散良好({spacing_pct:.2f}%，区间[{min_threshold:.2f}%, {max_threshold:.2f}%])"
            
            return {
                'spacing_pct': float(spacing_pct),
                'threshold_pct': float(min_threshold),  # 保持兼容性
                'min_threshold': float(min_threshold),
                'max_threshold': float(max_threshold),
                'is_divergent': is_divergent,
                'description': desc,
                'ma5': float(ma5),
                'ma20': float(ma20)
            }
            
        except Exception as e:
            logger.debug(f"均线发散度计算异常: {e}")
            return {
                'spacing_pct': 0,
                'threshold_pct': 0,
                'is_divergent': False,
                'description': '计算异常'
            }
    
    def check_buy_trend_filter(self, stock, closes, current_price, stock_type):
        """
        买入趋势过滤（硬指标）
        必须满足所有启用的趋势条件才能买入
        
        Args:
            stock: 股票代码
            closes: 历史收盘价
            current_price: 当前价格
            stock_type: 股票类型（large/mid/small）
        
        Returns:
            {
                'passed': 是否通过趋势过滤,
                'failed_reason': 未通过的原因（如果有）,
                'details': 详细信息
            }
        """
        try:
            # 如果趋势过滤未启用，直接通过
            if not self.trend_filter_config.get('enabled', True):
                return {'passed': True, 'failed_reason': None, 'details': {}}
            
            buy_config = self.trend_filter_config.get('buy', {})
            details = {}
            
            # 1. 价格趋势检查
            price_trend_config = buy_config.get('price_trend', {})
            if price_trend_config.get('enabled', True):
                lookback_days = price_trend_config.get('lookback_days', 10)
                min_slope = price_trend_config.get('min_slope', 0.0)
                min_strength = price_trend_config.get('min_strength', 0.4)
                
                price_trend = self.analyze_price_trend(closes, current_price, lookback_days)
                details['price_trend'] = price_trend
                
                if not price_trend['is_uptrend']:
                    return {
                        'passed': False,
                        'failed_reason': f"价格趋势向下: {price_trend['trend_description']}",
                        'details': details
                    }
                
                if price_trend['trend_strength'] < min_strength:
                    return {
                        'passed': False,
                        'failed_reason': f"价格趋势强度不足({price_trend['trend_strength']:.2f} < {min_strength})",
                        'details': details
                    }
            
            # 2. MA5趋势检查
            ma5_trend_config = buy_config.get('ma5_trend', {})
            if ma5_trend_config.get('enabled', True):
                lookback_days = ma5_trend_config.get('lookback_days', 5)
                min_slope_pct = ma5_trend_config.get('min_slope_pct', 0.0)
                
                ma5_trend = self.calculate_ma_slope(closes, 5, lookback_days, current_price)
                details['ma5_trend'] = ma5_trend
                
                if not ma5_trend['is_uptrend']:
                    return {
                        'passed': False,
                        'failed_reason': f"MA5趋势向下: {ma5_trend['description']}",
                        'details': details
                    }
                
                if ma5_trend['slope_pct'] < min_slope_pct:
                    return {
                        'passed': False,
                        'failed_reason': f"MA5斜率不足({ma5_trend['slope_pct']:.3f}% < {min_slope_pct}%)",
                        'details': details
                    }
            
            # 3. MA10趋势检查（可选）
            ma10_trend_config = buy_config.get('ma10_trend', {})
            if ma10_trend_config.get('enabled', False):
                lookback_days = ma10_trend_config.get('lookback_days', 5)
                min_slope_pct = ma10_trend_config.get('min_slope_pct', 0.0)
                
                ma10_trend = self.calculate_ma_slope(closes, 10, lookback_days, current_price)
                details['ma10_trend'] = ma10_trend
                
                if not ma10_trend['is_uptrend']:
                    return {
                        'passed': False,
                        'failed_reason': f"MA10趋势向下: {ma10_trend['description']}",
                        'details': details
                    }
            
            # 4. 均线发散度检查
            ma_divergence_config = buy_config.get('ma_divergence', {})
            if ma_divergence_config.get('enabled', True):
                divergence = self.check_ma_divergence(closes, current_price, stock_type)
                details['ma_divergence'] = divergence
                
                if not divergence['is_divergent']:
                    return {
                        'passed': False,
                        'failed_reason': f"均线发散度不足: {divergence['description']}",
                        'details': details
                    }
            
            # 所有检查通过
            return {
                'passed': True,
                'failed_reason': None,
                'details': details
            }
            
        except Exception as e:
            logger.error(f"{stock} 买入趋势过滤异常: {e}")
            # 异常时默认通过，避免阻止交易
            return {'passed': True, 'failed_reason': None, 'details': {}}
    
    def check_sell_trend_signal(self, stock, closes, current_price, volume_ratio):
        """
        卖出趋势信号检查（条件E：趋势转弱）
        
        Args:
            stock: 股票代码
            closes: 历史收盘价
            current_price: 当前价格
            volume_ratio: 当前量比
        
        Returns:
            {
                'should_sell': 是否应该卖出,
                'reason': 卖出原因（如果有）,
                'details': 详细信息
            }
        """
        try:
            # 如果趋势过滤未启用，不触发卖出
            if not self.trend_filter_config.get('enabled', True):
                return {'should_sell': False, 'reason': None, 'details': {}}
            
            # 获取当日数据（用于读取预计算的指标）
            today_data = self.today_data.get(stock, {})
            
            sell_config = self.trend_filter_config.get('sell', {})
            details = {}
            
            # 1. 价格趋势转弱检查
            price_trend_config = sell_config.get('price_trend', {})
            if price_trend_config.get('enabled', True):
                lookback_days = price_trend_config.get('lookback_days', 5)
                max_slope = price_trend_config.get('max_slope', 0.0)
                
                price_trend = self.analyze_price_trend(closes, current_price, lookback_days)
                details['price_trend'] = price_trend
                
                if price_trend['slope'] <= max_slope:
                    return {
                        'should_sell': True,
                        'reason': f"条件E-价格趋势转弱: {price_trend['trend_description']}",
                        'details': details
                    }
            
            # 2. MA5趋势转弱检查
            ma5_trend_config = sell_config.get('ma5_trend', {})
            if ma5_trend_config.get('enabled', True):
                lookback_days = ma5_trend_config.get('lookback_days', 3)
                max_slope_pct = ma5_trend_config.get('max_slope_pct', -0.2)
                
                ma5_trend = self.calculate_ma_slope(closes, 5, lookback_days, current_price)
                details['ma5_trend'] = ma5_trend
                
                if ma5_trend['slope_pct'] <= max_slope_pct:
                    return {
                        'should_sell': True,
                        'reason': f"条件E-MA5趋势转弱: {ma5_trend['description']}",
                        'details': details
                    }
            
            # 3. 方案C：均线发散度缩小检查（MA5-MA20间距）
            ma_divergence_config = sell_config.get('ma_divergence', {})
            if ma_divergence_config.get('enabled', False):
                max_spacing_map = ma_divergence_config.get('max_spacing_pct', {})
                
                # 获取股票类型（大/中/小盘）
                stock_type = today_data.get('stock_type', 'mid')
                max_spacing = max_spacing_map.get(stock_type, 0.5)
                
                # 使用预计算的均线发散度
                ma5 = today_data.get('ma5')
                ma20 = today_data.get('ma20')
                
                if ma5 is not None and ma20 is not None and ma20 > 0:
                    spacing_pct = (ma5 - ma20) / ma20 * 100
                    details['ma_divergence_sell'] = {
                        'spacing_pct': float(spacing_pct),
                        'threshold': max_spacing,
                        'stock_type': stock_type,
                        'ma5': float(ma5),
                        'ma20': float(ma20)
                    }
                    
                    # 均线发散度缩小到阈值以下，趋势减弱
                    if spacing_pct < max_spacing:
                                return {
                                    'should_sell': True,
                            'reason': f"条件E-均线发散缩小(MA5-MA20间距{spacing_pct:.2f}%<{max_spacing}%，{stock_type}盘股)",
                                'details': details
                            }
            
            # 4. 均线死叉检查（MA5下穿MA10）
            ma_death_cross_config = sell_config.get('ma_death_cross', {})
            if ma_death_cross_config.get('enabled', True):
                lookback_days = ma_death_cross_config.get('lookback_days', 3)
                
                if len(closes) >= 10 + lookback_days:
                    # 检测最近N天是否发生死叉
                    for i in range(1, lookback_days + 1):
                        idx = -i
                        # 前一天的MA5和MA10（这里需要检查，因为负索引可能超出范围）
                        prev_ma5_slice = closes[idx-5:idx]
                        prev_ma10_slice = closes[idx-10:idx]
                        
                        if len(prev_ma5_slice) < 5 or len(prev_ma10_slice) < 10:
                            continue  # 数据不足，跳过
                        
                        prev_ma5 = float(np.mean(prev_ma5_slice))
                        prev_ma10 = float(np.mean(prev_ma10_slice))
                        
                        # 当天的MA5和MA10
                        if i == 1:
                            # 使用预计算的最新MA值
                            curr_ma5 = today_data.get('ma5')
                            curr_ma10 = today_data.get('ma10')
                            
                            if curr_ma5 is None or curr_ma10 is None:
                                continue  # 未预计算，跳过
                        else:
                            curr_ma5_slice = closes[idx-4:idx+1]
                            curr_ma10_slice = closes[idx-9:idx+1]
                            
                            if len(curr_ma5_slice) < 5 or len(curr_ma10_slice) < 10:
                                continue  # 数据不足，跳过
                            
                            curr_ma5 = float(np.mean(curr_ma5_slice))
                            curr_ma10 = float(np.mean(curr_ma10_slice))
                        
                        # 检测死叉：前一天MA5>MA10，当天MA5<MA10
                        if prev_ma5 > prev_ma10 and curr_ma5 < curr_ma10:
                            details['ma_death_cross'] = {
                                'cross_day': i,
                                'prev_ma5': float(prev_ma5),
                                'prev_ma10': float(prev_ma10),
                                'curr_ma5': float(curr_ma5),
                                'curr_ma10': float(curr_ma10)
                            }
                            
                            return {
                                'should_sell': True,
                                'reason': f"条件E-均线死叉(MA5{curr_ma5:.2f}下穿MA10{curr_ma10:.2f})",
                                'details': details
                            }
            
            # 没有触发任何卖出信号
            return {
                'should_sell': False,
                'reason': None,
                'details': details
            }
            
        except Exception as e:
            logger.error(f"{stock} 卖出趋势信号检查异常: {e}")
            return {'should_sell': False, 'reason': None, 'details': {}}
    
    def analyze_daily_volume_ratio(self, stock, today_data, hist_np):
        """
        分析当日量比：考虑交易时间的影响
        根据用户建议：当日的成交量比昨日的放大（应该考虑当日量比，因为当日的交易时间长短的问题）
        """
        try:
            # 获取优化后的量比（已考虑交易时间）
            volume_ratio = self.get_volume_ratio(stock)
            
            # 如果API量比获取失败，使用扩展周期计算
            if volume_ratio <= 0:
                volumes = hist_np.get('volume')
                if volumes is not None and len(volumes) >= 10:
                    # 扩展到10日平均，而不是5日
                    vol_ma10 = float(np.mean(volumes[-10:]))
                    volume_ratio = today_data['volume'] / vol_ma10 if vol_ma10 > 0 else 1.0
                else:
                    volume_ratio = 1.0
            
            # 判断是否放大
            is_amplified = volume_ratio >= 1.2  # 至少20%的放大
            
            if is_amplified:
                if volume_ratio >= 2.0:
                    desc = f"显著放量(量比{volume_ratio:.2f})"
                elif volume_ratio >= 1.5:
                    desc = f"明显放量(量比{volume_ratio:.2f})"
                else:
                    desc = f"温和放量(量比{volume_ratio:.2f})"
            else:
                desc = f"量比偏低({volume_ratio:.2f}),不符合放量要求"
            
            return {
                'is_amplified': is_amplified,
                'volume_ratio': volume_ratio,
                'description': desc
            }
            
        except Exception as e:
            logger.debug(f"当日量比分析异常: {e}")
            return {'is_amplified': False, 'volume_ratio': 1.0, 'description': '分析异常'}
    
    def check_macd_signals(self, stock, today_data, hist_np):
        """
        完整的MACD指标分析，包括背离检测
        MACD背离是重要的趋势反转信号：
        - 底背离：价格创新低，MACD不创新低 → 看涨信号
        - 顶背离：价格创新高，MACD不创新高 → 看跌信号
        """
        try:
            closes = hist_np.get('close')
            if closes is None or len(closes) < 35:  # 需要更多数据计算MACD
                return {'valid': True, 'reason': 'MACD数据不足，跳过检查'}
            
            # 构建包含今日价格的完整价格序列
            prices = list(closes) + [today_data['last']]
            prices_array = np.array(prices)
            
            # 1. 计算标准MACD指标
            macd_result = self.calculate_macd(prices_array)
            if macd_result is None:
                return {'valid': True, 'reason': 'MACD计算失败，默认通过'}
            
            dif_line = macd_result['dif']
            dea_line = macd_result['dea'] 
            macd_histogram = macd_result['macd']
            
            # 2. 当前MACD状态分析
            current_dif = dif_line[-1]
            current_dea = dea_line[-1]
            current_macd = macd_histogram[-1]
            
            # MACD基本信号
            if current_dif > current_dea:
                basic_signal = "多头"
                basic_valid = True
            else:
                basic_signal = "空头" 
                basic_valid = False
            
            # 3. 背离分析（重点功能）
            divergence_result = self.detect_macd_divergence(prices_array, dif_line, dea_line)
            
            # 4. 综合MACD评估
            macd_strength = abs(current_dif - current_dea)  # MACD强度
            macd_trend = "上升" if len(dif_line) >= 2 and dif_line[-1] > dif_line[-2] else "下降"
            
            # 5. 最终判断逻辑
            final_valid = basic_valid
            warning_signals = []
            
            # 背离修正
            if divergence_result['has_divergence']:
                div_type = divergence_result['type']
                div_strength = divergence_result['strength']
                
                if div_type == '顶背离':
                    final_valid = False  # 顶背离看跌
                    warning_signals.append(f"检测到{div_strength}顶背离")
                elif div_type == '底背离':
                    final_valid = True   # 底背离看涨
                    warning_signals.append(f"检测到{div_strength}底背离")
            
            # MACD柱状图分析
            if len(macd_histogram) >= 3:
                recent_macd_trend = "转强" if macd_histogram[-1] > macd_histogram[-2] else "转弱"
                if recent_macd_trend == "转弱" and basic_signal == "多头":
                    warning_signals.append("MACD柱状图转弱")
            
            # 构建分析结果
            analysis_msg = f"MACD({basic_signal}趋势{macd_trend})"
            if divergence_result['has_divergence']:
                analysis_msg += f" - {divergence_result['type']}({divergence_result['strength']})"
            
            logger.info(f"{stock} MACD分析: {analysis_msg}")
            if warning_signals:
                logger.info(f"{stock} MACD警告: {', '.join(warning_signals)}")
            
            return {
                'valid': final_valid,
                'basic_signal': basic_signal,
                'macd_trend': macd_trend,
                'divergence': divergence_result,
                'current_dif': current_dif,
                'current_dea': current_dea,
                'current_macd': current_macd,
                'macd_strength': macd_strength,
                'warnings': warning_signals,
                'reason': analysis_msg
            }
            
        except Exception as e:
            logger.error(f"{stock} MACD分析失败: {str(e)}")
            return {'valid': True, 'reason': 'MACD分析异常，默认通过'}
    
    def calculate_macd(self, prices, fast_period=12, slow_period=26, signal_period=9):
        """
        计算标准MACD指标
        MACD = DIF - DEA
        DIF = EMA12 - EMA26 
        DEA = EMA9(DIF)
        """
        try:
            if len(prices) < slow_period + signal_period:
                return None
                
            # 计算EMA
            ema_fast = self.calculate_ema(prices, fast_period)
            ema_slow = self.calculate_ema(prices, slow_period)
            
            # 计算DIF线 (快线-慢线)
            dif = ema_fast - ema_slow
            
            # 计算DEA线 (DIF的EMA)
            dea = self.calculate_ema(dif, signal_period)
            
            # 计算MACD柱状图 (DIF-DEA)*2
            macd_histogram = (dif - dea) * 2
            
            return {
                'dif': dif,
                'dea': dea, 
                'macd': macd_histogram
            }
            
        except Exception as e:
            logger.debug(f"MACD计算异常: {e}")
            return None
    
    def calculate_ema(self, prices, period):
        """
        计算指数移动平均线(EMA)
        EMA = (收盘价 - 昨日EMA) × 2/(N+1) + 昨日EMA
        """
        prices = np.array(prices)
        ema = np.zeros_like(prices, dtype=float)
        
        # 第一个值使用SMA
        ema[period-1] = np.mean(prices[:period])
        
        # 平滑系数
        multiplier = 2.0 / (period + 1)
        
        # 计算后续EMA值
        for i in range(period, len(prices)):
            ema[i] = (prices[i] - ema[i-1]) * multiplier + ema[i-1]
        
        return ema
    
    def detect_macd_divergence(self, prices, dif_line, dea_line, lookback_period=20):
        """
        检测MACD背离现象
        背离是指价格走势与MACD指标走势相反的技术现象
        """
        try:
            if len(prices) < lookback_period or len(dif_line) < lookback_period:
                return {'has_divergence': False, 'type': None, 'strength': None}
            
            # 取最近的数据进行分析
            recent_prices = prices[-lookback_period:]
            recent_dif = dif_line[-lookback_period:]
            
            # 寻找价格和DIF的高低点
            price_peaks = self.find_peaks_and_troughs(recent_prices)
            dif_peaks = self.find_peaks_and_troughs(recent_dif)
            
            # 检测顶背离：价格创新高，MACD不创新高
            top_divergence = self.check_top_divergence(price_peaks, dif_peaks)
            
            # 检测底背离：价格创新低，MACD不创新低  
            bottom_divergence = self.check_bottom_divergence(price_peaks, dif_peaks)
            
            # 确定背离类型和强度
            if top_divergence['detected']:
                return {
                    'has_divergence': True,
                    'type': '顶背离',
                    'strength': top_divergence['strength'],
                    'description': '价格创新高但MACD未创新高，看跌信号'
                }
            elif bottom_divergence['detected']:
                return {
                    'has_divergence': True, 
                    'type': '底背离',
                    'strength': bottom_divergence['strength'],
                    'description': '价格创新低但MACD未创新低，看涨信号'
                }
            else:
                return {'has_divergence': False, 'type': None, 'strength': None}
                
        except Exception as e:
            logger.debug(f"MACD背离检测异常: {e}")
            return {'has_divergence': False, 'type': None, 'strength': None}
    
    def find_peaks_and_troughs(self, data, min_distance=3):
        """
        寻找数据序列中的波峰和波谷
        """
        try:
            data = np.array(data)
            peaks = []
            troughs = []
            
            for i in range(min_distance, len(data) - min_distance):
                # 检查是否为波峰
                is_peak = True
                for j in range(i - min_distance, i + min_distance + 1):
                    if j != i and data[j] >= data[i]:
                        is_peak = False
                        break
                if is_peak:
                    peaks.append({'index': i, 'value': data[i]})
                
                # 检查是否为波谷
                is_trough = True
                for j in range(i - min_distance, i + min_distance + 1):
                    if j != i and data[j] <= data[i]:
                        is_trough = False
                        break
                if is_trough:
                    troughs.append({'index': i, 'value': data[i]})
            
            return {'peaks': peaks, 'troughs': troughs}
            
        except Exception as e:
            logger.debug(f"波峰波谷识别异常: {e}")
            return {'peaks': [], 'troughs': []}
    
    def find_corresponding_peaks(self, price_peaks, dif_peaks, time_window=5):
        """
        找到价格和MACD波峰之间的对应关系
        解决用户提出的问题：A点对应的5元，B点波峰对应的3元
        这种情况下，必须确保比较的是同一时期的波峰
        
        Args:
            price_peaks: 价格波峰列表
            dif_peaks: MACD DIF波峰列表 
            time_window: 时间窗口，允许的最大时间偏差（天数）
        
        Returns:
            有效波峰配对列表
        """
        try:
            valid_pairs = []
            
            # 为每个价格波峰找对应的MACD波峰
            for price_peak in price_peaks:
                price_index = price_peak['index']
                price_value = price_peak['value']
                
                # 在时间窗口内找最近的MACD波峰
                best_match = None
                min_distance = float('inf')
                
                for dif_peak in dif_peaks:
                    dif_index = dif_peak['index']
                    dif_value = dif_peak['value']
                    
                    # 计算时间距离
                    time_distance = abs(price_index - dif_index)
                    
                    # 在时间窗口内且距离最近
                    if time_distance <= time_window and time_distance < min_distance:
                        min_distance = time_distance
                        best_match = {
                            'time_index': max(price_index, dif_index),  # 使用较晚的时间作为配对时间
                            'price_index': price_index,
                            'price_value': price_value,
                            'dif_index': dif_index,
                            'dif_value': dif_value,
                            'time_distance': time_distance
                        }
                
                if best_match:
                    valid_pairs.append(best_match)
            
            # 按时间顺序排序
            valid_pairs = sorted(valid_pairs, key=lambda x: x['time_index'])
            
            logger.debug(f"波峰配对结果: 找到{len(valid_pairs)}个有效配对")
            for i, pair in enumerate(valid_pairs):
                logger.debug(f"  配对{i+1}: 价格[{pair['price_index']}]={pair['price_value']:.2f}, MACD[{pair['dif_index']}]={pair['dif_value']:.4f}, 时间偏差={pair['time_distance']}")
            
            return valid_pairs
            
        except Exception as e:
            logger.debug(f"波峰配对异常: {e}")
            return []
    
    def find_corresponding_troughs(self, price_troughs, dif_troughs, time_window=5):
        """
        找到价格和MACD波谷之间的对应关系
        与波峰配对类似，但针对波谷（低点）
        """
        try:
            valid_pairs = []
            
            # 为每个价格波谷找对应的MACD波谷
            for price_trough in price_troughs:
                price_index = price_trough['index']
                price_value = price_trough['value']
                
                # 在时间窗口内找最近的MACD波谷
                best_match = None
                min_distance = float('inf')
                
                for dif_trough in dif_troughs:
                    dif_index = dif_trough['index']
                    dif_value = dif_trough['value']
                    
                    # 计算时间距离
                    time_distance = abs(price_index - dif_index)
                    
                    # 在时间窗口内且距离最近
                    if time_distance <= time_window and time_distance < min_distance:
                        min_distance = time_distance
                        best_match = {
                            'time_index': max(price_index, dif_index),  # 使用较晚的时间作为配对时间
                            'price_index': price_index,
                            'price_value': price_value,
                            'dif_index': dif_index,
                            'dif_value': dif_value,
                            'time_distance': time_distance
                        }
                
                if best_match:
                    valid_pairs.append(best_match)
            
            # 按时间顺序排序
            valid_pairs = sorted(valid_pairs, key=lambda x: x['time_index'])
            
            logger.debug(f"波谷配对结果: 找到{len(valid_pairs)}个有效配对")
            for i, pair in enumerate(valid_pairs):
                logger.debug(f"  配对{i+1}: 价格[{pair['price_index']}]={pair['price_value']:.2f}, MACD[{pair['dif_index']}]={pair['dif_value']:.4f}, 时间偏差={pair['time_distance']}")
            
            return valid_pairs
            
        except Exception as e:
            logger.debug(f"波谷配对异常: {e}")
            return []

    def check_top_divergence(self, price_peaks, dif_peaks):
        """
        检查顶背离：价格创新高，MACD不创新高
        改进版：基于时间窗口的波峰配对，确保比较的是同一时期的波峰
        """
        try:
            price_highs = price_peaks['peaks']
            dif_highs = dif_peaks['peaks']
            
            if len(price_highs) < 2 or len(dif_highs) < 2:
                return {'detected': False, 'strength': None}
            
            # 找到有效的波峰配对
            valid_pairs = self.find_corresponding_peaks(price_highs, dif_highs)
            
            if len(valid_pairs) < 2:
                return {'detected': False, 'strength': None}
            
            # 取最近的两个有效配对
            recent_pairs = sorted(valid_pairs, key=lambda x: x['time_index'])[-2:]
            
            pair_a = recent_pairs[0]  # 较早的波峰对
            pair_b = recent_pairs[1]  # 较新的波峰对
            
            # 检查顶背离：价格创新高，MACD不创新高
            price_new_high = pair_b['price_value'] > pair_a['price_value']
            macd_not_new_high = pair_b['dif_value'] < pair_a['dif_value']
            
            if price_new_high and macd_not_new_high:
                # 计算背离强度
                price_diff = pair_b['price_value'] - pair_a['price_value']
                macd_diff = pair_a['dif_value'] - pair_b['dif_value']
                
                # 背离强度评估
                price_change_pct = (price_diff / pair_a['price_value']) * 100
                
                if macd_diff > 0.05 or price_change_pct > 5.0:
                    strength = "强"
                elif macd_diff > 0.02 or price_change_pct > 2.0:
                    strength = "中"
                else:
                    strength = "弱"
                
                logger.debug(f"顶背离检测: A点(价格{pair_a['price_value']:.2f},MACD{pair_a['dif_value']:.4f}) -> B点(价格{pair_b['price_value']:.2f},MACD{pair_b['dif_value']:.4f})")
                return {
                    'detected': True, 
                    'strength': strength,
                    'pair_a': pair_a,
                    'pair_b': pair_b,
                    'price_change_pct': price_change_pct,
                    'macd_diff': macd_diff
                }
            
            return {'detected': False, 'strength': None}
            
        except Exception as e:
            logger.debug(f"顶背离检测异常: {e}")
            return {'detected': False, 'strength': None}
    
    def check_bottom_divergence(self, price_peaks, dif_peaks):
        """
        检查底背离：价格创新低，MACD不创新低
        改进版：基于时间窗口的波谷配对，确保比较的是同一时期的波谷
        """
        try:
            price_lows = price_peaks['troughs']
            dif_lows = dif_peaks['troughs']
            
            if len(price_lows) < 2 or len(dif_lows) < 2:
                return {'detected': False, 'strength': None}
            
            # 找到有效的波谷配对
            valid_pairs = self.find_corresponding_troughs(price_lows, dif_lows)
            
            if len(valid_pairs) < 2:
                return {'detected': False, 'strength': None}
            
            # 取最近的两个有效配对
            recent_pairs = sorted(valid_pairs, key=lambda x: x['time_index'])[-2:]
            
            pair_a = recent_pairs[0]  # 较早的波谷对
            pair_b = recent_pairs[1]  # 较新的波谷对
            
            # 检查底背离：价格创新低，MACD不创新低
            price_new_low = pair_b['price_value'] < pair_a['price_value']
            macd_not_new_low = pair_b['dif_value'] > pair_a['dif_value']
            
            if price_new_low and macd_not_new_low:
                # 计算背离强度
                price_diff = pair_a['price_value'] - pair_b['price_value']
                macd_diff = pair_b['dif_value'] - pair_a['dif_value']
                
                # 背离强度评估
                price_change_pct = (price_diff / pair_a['price_value']) * 100
                
                if macd_diff > 0.05 or price_change_pct > 5.0:
                    strength = "强"
                elif macd_diff > 0.02 or price_change_pct > 2.0:
                    strength = "中"
                else:
                    strength = "弱"
                
                logger.debug(f"底背离检测: A点(价格{pair_a['price_value']:.2f},MACD{pair_a['dif_value']:.4f}) -> B点(价格{pair_b['price_value']:.2f},MACD{pair_b['dif_value']:.4f})")
                return {
                    'detected': True, 
                    'strength': strength,
                    'pair_a': pair_a,
                    'pair_b': pair_b,
                    'price_change_pct': price_change_pct,
                    'macd_diff': macd_diff
                }
            
            return {'detected': False, 'strength': None}
            
        except Exception as e:
            logger.debug(f"底背离检测异常: {e}")
            return {'detected': False, 'strength': None}

if __name__ == "__main__":
    print("=" * 60)
    print("突破策略启动")
    print("=" * 60)

    try:
        # 禁用xtQuant的连接成功消息
        xtdata.enable_hello = False

        # 创建策略实例
        strategy = BreakoutStrategy()
    except Exception as e:
        logger.error(f"策略初始化失败: {str(e)}")
        logger.info("将在5秒后退出...")
        time.sleep(5)